| 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
|
| }
|
|
|