Index: fuzzer/go/frontend/data/result.go |
diff --git a/fuzzer/go/frontend/data/result.go b/fuzzer/go/frontend/data/result.go |
index 8fe1b07494032f201118ec5d8811b4def6a3cdf4..7dfb7cb5a67996eea5ea8c785a7ec1175b71e559 100644 |
--- a/fuzzer/go/frontend/data/result.go |
+++ b/fuzzer/go/frontend/data/result.go |
@@ -2,55 +2,84 @@ package data |
import ( |
"fmt" |
+ "regexp" |
+ "sort" |
"strings" |
+ |
+ "go.skia.org/infra/fuzzer/go/common" |
) |
// Represents the metadata about a crash, hopefully easing debugging. |
type FuzzResult struct { |
- DebugStackTrace StackTrace |
- ReleaseStackTrace StackTrace |
- DebugDump string |
- ReleaseDump string |
- DebugStdErr string |
- ReleaseStdErr string |
- Flags FuzzFlag |
+ Debug BuildData |
+ Release BuildData |
} |
-// A bit mask representing what happened when a fuzz ran against a debug version of Skia and a release version |
+// BuildData represents the results of parsing a given skia build's output. |
+type BuildData struct { |
+ OutputFiles |
+ StackTrace StackTrace |
+ Flags FuzzFlag |
+} |
+ |
+// OutputFiles are the files output by the analysis |
+type OutputFiles struct { |
+ Asan string |
+ Dump string |
+ StdErr string |
+} |
+ |
+// GCSPackage is a struct containing all the pieces of a fuzz that exist in Google Storage. |
+type GCSPackage struct { |
+ Name string |
+ FuzzCategory string |
+ Debug OutputFiles |
+ Release OutputFiles |
+} |
+ |
+// A bit mask representing what happened when a fuzz ran against Skia. |
type FuzzFlag int |
const ( |
- DebugCrashed FuzzFlag = 1 << iota |
- ReleaseCrashed |
- DebugFailedGracefully |
- ReleaseFailedGracefully |
- DebugAssertionViolated |
- DebugBadAlloc |
- ReleaseBadAlloc |
- DebugTimedOut |
- ReleaseTimedOut |
- DebugNoStackTrace |
- ReleaseNoStackTrace |
- DebugOther |
- ReleaseOther |
+ TerminatedGracefully FuzzFlag = 1 << iota |
+ ClangCrashed |
+ ASANCrashed |
+ AssertionViolated |
+ BadAlloc |
+ NoStackTrace |
+ SKAbortHit |
+ TimedOut |
+ Other |
+ |
+ ASAN_GlobalBufferOverflow |
+ ASAN_HeapBufferOverflow |
+ ASAN_StackBufferOverflow |
+ ASAN_HeapUseAfterFree |
+ |
+ SKPICTURE_DuringRendering |
) |
var flagNames = []string{ |
- "DebugCrashed", |
- "ReleaseCrashed", |
- "DebugFailedGracefully", |
- "ReleaseFailedGracefully", |
- "DebugAssertionViolated", |
- "DebugBadAlloc", |
- "ReleaseBadAlloc", |
- "DebugTimedOut", |
- "ReleaseTimedOut", |
- "DebugNoStackTrace", |
- "ReleaseNoStackTrace", |
- "DebugOther", |
- "ReleaseOther", |
+ "FailedGracefully", |
+ "ClangCrashed", |
+ "ASANCrashed", |
+ "AssertionViolated", |
+ "BadAlloc", |
+ "NoStackTrace", |
+ "SKAbortHit", |
+ "TimedOut", |
+ "Other", |
+ |
+ "ASAN_global-buffer-overflow", |
+ "ASAN_heap-buffer-overflow", |
+ "ASAN_stack-buffer-overflow", |
+ "ASAN_heap-use-after-free", |
+ |
+ "SKPICTURE_DuringRendering", |
} |
+// ToHumanReadableFlags creates a sorted slice of strings that represents the flags. The slice |
+// is sorted by unicode points, as per sort.Strings(). |
func (f FuzzFlag) ToHumanReadableFlags() []string { |
flags := make([]string, 0) |
i := 0 |
@@ -60,6 +89,8 @@ func (f FuzzFlag) ToHumanReadableFlags() []string { |
} |
i++ |
} |
+ // Front end filtering logic will expect the flags to be in alphabetical order. |
+ sort.Strings(flags) |
return flags |
} |
@@ -67,59 +98,155 @@ func (f FuzzFlag) String() string { |
return fmt.Sprintf("FuzzFlag: %016b (%d) [%s]", f, f, strings.Join(f.ToHumanReadableFlags(), " | ")) |
} |
-func ParseFuzzResult(debugDump, debugErr, releaseDump, releaseErr string) FuzzResult { |
- result := FuzzResult{ |
- DebugDump: debugDump, |
- DebugStackTrace: ParseStackTrace(debugDump), |
- DebugStdErr: debugErr, |
- ReleaseDump: releaseDump, |
- ReleaseStackTrace: ParseStackTrace(releaseDump), |
- ReleaseStdErr: releaseErr, |
- Flags: 0, //dummy value, to be updated shortly |
- } |
- result.computeFlags() |
+// ParseGCSPackage parses the results of analysis of a fuzz and creates a FuzzResult with it. |
+// This includes parsing the stacktraces and computing the flags about the fuzz. |
+func ParseGCSPackage(g GCSPackage) FuzzResult { |
+ result := FuzzResult{} |
+ result.Debug.Asan = g.Debug.Asan |
+ result.Debug.Dump = g.Debug.Dump |
+ result.Debug.StdErr = g.Debug.StdErr |
+ result.Debug.StackTrace = getStackTrace(g.Debug.Asan, g.Debug.Dump) |
+ result.Release.Asan = g.Release.Asan |
+ result.Release.Dump = g.Release.Dump |
+ result.Release.StdErr = g.Release.StdErr |
+ result.Release.StackTrace = getStackTrace(g.Release.Asan, g.Release.Dump) |
+ result.computeFlags(g.FuzzCategory) |
return result |
} |
-func (r *FuzzResult) computeFlags() { |
- flags := FuzzFlag(0) |
+// getStackTrace creates a StackTrace output from one of the two dumps given. It first tries to |
+// use the AddressSanitizer dump, with the Clang dump as a fallback. |
+func getStackTrace(asan, dump string) StackTrace { |
+ if asanCrashed(asan) { |
+ return parseASANStackTrace(asan) |
+ } |
+ return parseCatchsegvStackTrace(dump) |
+} |
+ |
+// computeFlags parses the raw data to set both the Debug and Release flags. |
+func (r *FuzzResult) computeFlags(category string) { |
+ r.Debug.Flags = parseAll(category, &r.Debug) |
+ r.Release.Flags = parseAll(category, &r.Release) |
+} |
- if r.DebugDump != "" { |
- flags |= DebugCrashed |
- if r.DebugStackTrace.IsEmpty() { |
- flags |= DebugNoStackTrace |
+// parseAll looks at the three input files and parses the results, based on the category. The |
+// category allows for specialized flags, like SKPICTURE_DuringRendering. |
+func parseAll(category string, data *BuildData) FuzzFlag { |
+ f := FuzzFlag(0) |
+ // Check for SKAbort message |
+ if strings.Contains(data.Asan, "fatal error") { |
+ f |= ASANCrashed |
+ f |= SKAbortHit |
+ if data.StackTrace.IsEmpty() { |
+ data.StackTrace = extractSkAbortTrace(data.StdErr) |
} |
} |
- |
- if r.ReleaseDump != "" { |
- flags |= ReleaseCrashed |
- if r.ReleaseStackTrace.IsEmpty() { |
- flags |= ReleaseNoStackTrace |
+ if strings.Contains(data.StdErr, "fatal error") { |
+ f |= ClangCrashed |
+ f |= SKAbortHit |
+ if data.StackTrace.IsEmpty() { |
+ data.StackTrace = extractSkAbortTrace(data.StdErr) |
+ } |
+ } |
+ // If no sk abort message and no evidence of crashes, we either terminated gracefully or |
+ // timed out. |
+ if f == 0 && !asanCrashed(data.Asan) && !clangDumped(data.Dump) { |
+ if strings.Contains(data.Asan, "[terminated]") && strings.Contains(data.StdErr, "[terminated]") { |
+ return TerminatedGracefully |
} |
+ return TimedOut |
} |
- if r.DebugStdErr == "" && r.DebugDump == "" { |
- flags |= DebugTimedOut |
- } else if strings.Contains(r.DebugStdErr, "failed assertion") { |
- flags |= DebugAssertionViolated |
- } else if strings.Contains(r.DebugStdErr, `terminate called after throwing an instance of 'std::bad_alloc'`) { |
- flags |= DebugCrashed | DebugBadAlloc |
- } else if strings.Contains(r.DebugStdErr, `Success`) { |
- flags |= DebugFailedGracefully |
- } else if r.DebugStdErr != "" { |
- flags |= DebugOther |
+ // Look for clues from the various dumps. |
+ f |= parseAsan(category, data.Asan) |
+ f |= parseCatchsegv(category, data.Dump, data.StdErr) |
+ if f == 0 { |
+ // I don't know what this means (yet). |
+ return Other |
+ } |
+ if data.StackTrace.IsEmpty() { |
+ f |= NoStackTrace |
} |
+ return f |
+} |
- if r.ReleaseStdErr == "" && r.ReleaseDump == "" { |
- flags |= ReleaseTimedOut |
- } else if strings.Contains(r.ReleaseStdErr, `terminate called after throwing an instance of 'std::bad_alloc'`) { |
- flags |= ReleaseCrashed | ReleaseBadAlloc |
- } else if strings.Contains(r.ReleaseStdErr, `Success`) { |
- flags |= ReleaseFailedGracefully |
- } else if r.ReleaseStdErr != "" { |
- flags |= ReleaseOther |
+// parseAsan returns the flags discovered while looking through the AddressSanitizer output. This |
+// includes things like ASAN_GlobalBufferOverflow. |
+func parseAsan(category, asan string) FuzzFlag { |
+ f := FuzzFlag(0) |
+ if !asanCrashed(asan) { |
+ return f |
+ } |
+ f |= ASANCrashed |
+ if strings.Contains(asan, "failed assertion") { |
+ f |= AssertionViolated |
+ } |
+ if strings.Contains(asan, "global-buffer-overflow") { |
+ f |= ASAN_GlobalBufferOverflow |
+ } |
+ if strings.Contains(asan, "heap-buffer-overflow") { |
+ f |= ASAN_HeapBufferOverflow |
+ } |
+ if strings.Contains(asan, "stack-buffer-overflow") { |
+ f |= ASAN_StackBufferOverflow |
+ } |
+ if strings.Contains(asan, "heap-use-after-free") { |
+ f |= ASAN_HeapUseAfterFree |
+ } |
+ if strings.Contains(asan, "AddressSanitizer failed to allocate") { |
+ f |= BadAlloc |
} |
- r.Flags = flags |
+ // Split off the stderr that happened before the crash. |
+ errs := strings.Split(asan, "=================") |
+ if len(errs) > 0 { |
+ err := errs[0] |
+ if category == "skpicture" && strings.Contains(err, "Rendering") { |
+ f |= SKPICTURE_DuringRendering |
+ } |
+ } |
+ return f |
+} |
+ |
+// asanCrashed returns true if the asan output is consistent with a crash. |
+func asanCrashed(asan string) bool { |
+ return strings.Contains(asan, "ERROR: AddressSanitizer:") |
+} |
+ |
+// parseAsan returns the flags discovered while looking through the Clang dump and standard error. |
+// This includes things like |
+func parseCatchsegv(category, dump, err string) FuzzFlag { |
+ f := FuzzFlag(0) |
+ if !clangDumped(dump) && strings.Contains(err, "[terminated]") { |
+ return f |
+ } |
+ f |= ClangCrashed |
+ if strings.Contains(err, "failed assertion") { |
+ f |= AssertionViolated |
+ } |
+ if category == "skpicture" && strings.Contains(err, "Rendering") { |
+ f |= SKPICTURE_DuringRendering |
+ } |
+ if strings.Contains(err, "std::bad_alloc") { |
+ f |= BadAlloc |
+ } |
+ return f |
+} |
+ |
+// clangDumped returns true if the clang output is consistent with a crash, that is, non empty. |
+func clangDumped(dump string) bool { |
+ return len(dump) != 0 |
+} |
+ |
+var skAbortStackTraceLine = regexp.MustCompile(`(?:\.\./)+(?P<package>(?:\w+/)+)(?P<file>.+):(?P<line>\d+): fatal error`) |
+ |
+// extractSkAbortTrace looks for the fatal error string indicative of the SKAbort termination |
+// and tries to pull out the stacktrace frame on which it happened. |
+func extractSkAbortTrace(err string) StackTrace { |
+ st := StackTrace{} |
+ if match := skAbortStackTraceLine.FindStringSubmatch(err); match != nil { |
+ st.Frames = append(st.Frames, FullStackFrame(match[1], match[2], common.UNKNOWN_FUNCTION, safeParseInt(match[3]))) |
+ } |
+ return st |
} |