| OLD | NEW |
| (Empty) |
| 1 package data | |
| 2 | |
| 3 import ( | |
| 4 "fmt" | |
| 5 "regexp" | |
| 6 "sort" | |
| 7 "strings" | |
| 8 | |
| 9 "go.skia.org/infra/fuzzer/go/common" | |
| 10 ) | |
| 11 | |
| 12 // Represents the metadata about a crash, hopefully easing debugging. | |
| 13 type FuzzResult struct { | |
| 14 Debug BuildData | |
| 15 Release BuildData | |
| 16 } | |
| 17 | |
| 18 // BuildData represents the results of parsing a given skia build's output. | |
| 19 type BuildData struct { | |
| 20 OutputFiles | |
| 21 StackTrace StackTrace | |
| 22 Flags FuzzFlag | |
| 23 } | |
| 24 | |
| 25 // OutputFiles are the files output by the analysis | |
| 26 type OutputFiles struct { | |
| 27 Asan string | |
| 28 Dump string | |
| 29 StdErr string | |
| 30 } | |
| 31 | |
| 32 // GCSPackage is a struct containing all the pieces of a fuzz that exist in Goog
le Storage. | |
| 33 type GCSPackage struct { | |
| 34 Name string | |
| 35 FuzzCategory string | |
| 36 Debug OutputFiles | |
| 37 Release OutputFiles | |
| 38 } | |
| 39 | |
| 40 // A bit mask representing what happened when a fuzz ran against Skia. | |
| 41 type FuzzFlag int | |
| 42 | |
| 43 const ( | |
| 44 TerminatedGracefully FuzzFlag = 1 << iota | |
| 45 ClangCrashed | |
| 46 ASANCrashed | |
| 47 AssertionViolated | |
| 48 BadAlloc | |
| 49 NoStackTrace | |
| 50 SKAbortHit | |
| 51 TimedOut | |
| 52 Other | |
| 53 | |
| 54 ASAN_GlobalBufferOverflow | |
| 55 ASAN_HeapBufferOverflow | |
| 56 ASAN_StackBufferOverflow | |
| 57 ASAN_HeapUseAfterFree | |
| 58 | |
| 59 SKPICTURE_DuringRendering | |
| 60 ) | |
| 61 | |
| 62 var flagNames = []string{ | |
| 63 "FailedGracefully", | |
| 64 "ClangCrashed", | |
| 65 "ASANCrashed", | |
| 66 "AssertionViolated", | |
| 67 "BadAlloc", | |
| 68 "NoStackTrace", | |
| 69 "SKAbortHit", | |
| 70 "TimedOut", | |
| 71 "Other", | |
| 72 | |
| 73 "ASAN_global-buffer-overflow", | |
| 74 "ASAN_heap-buffer-overflow", | |
| 75 "ASAN_stack-buffer-overflow", | |
| 76 "ASAN_heap-use-after-free", | |
| 77 | |
| 78 "SKPICTURE_DuringRendering", | |
| 79 } | |
| 80 | |
| 81 // ToHumanReadableFlags creates a sorted slice of strings that represents the fl
ags. The slice | |
| 82 // is sorted by unicode points, as per sort.Strings(). | |
| 83 func (f FuzzFlag) ToHumanReadableFlags() []string { | |
| 84 flags := make([]string, 0) | |
| 85 i := 0 | |
| 86 for mask := 1; mask < (2 << 16); mask *= 2 { | |
| 87 if int(f)&mask != 0 { | |
| 88 flags = append(flags, flagNames[i]) | |
| 89 } | |
| 90 i++ | |
| 91 } | |
| 92 // Front end filtering logic will expect the flags to be in alphabetical
order. | |
| 93 sort.Strings(flags) | |
| 94 return flags | |
| 95 } | |
| 96 | |
| 97 func (f FuzzFlag) String() string { | |
| 98 return fmt.Sprintf("FuzzFlag: %016b (%d) [%s]", f, f, strings.Join(f.ToH
umanReadableFlags(), " | ")) | |
| 99 } | |
| 100 | |
| 101 // ParseGCSPackage parses the results of analysis of a fuzz and creates a FuzzRe
sult with it. | |
| 102 // This includes parsing the stacktraces and computing the flags about the fuzz. | |
| 103 func ParseGCSPackage(g GCSPackage) FuzzResult { | |
| 104 result := FuzzResult{} | |
| 105 result.Debug.Asan = g.Debug.Asan | |
| 106 result.Debug.Dump = g.Debug.Dump | |
| 107 result.Debug.StdErr = g.Debug.StdErr | |
| 108 result.Debug.StackTrace = getStackTrace(g.Debug.Asan, g.Debug.Dump) | |
| 109 result.Release.Asan = g.Release.Asan | |
| 110 result.Release.Dump = g.Release.Dump | |
| 111 result.Release.StdErr = g.Release.StdErr | |
| 112 result.Release.StackTrace = getStackTrace(g.Release.Asan, g.Release.Dump
) | |
| 113 result.computeFlags(g.FuzzCategory) | |
| 114 | |
| 115 return result | |
| 116 } | |
| 117 | |
| 118 // getStackTrace creates a StackTrace output from one of the two dumps given. I
t first tries to | |
| 119 // use the AddressSanitizer dump, with the Clang dump as a fallback. | |
| 120 func getStackTrace(asan, dump string) StackTrace { | |
| 121 if asanCrashed(asan) { | |
| 122 return parseASANStackTrace(asan) | |
| 123 } | |
| 124 return parseCatchsegvStackTrace(dump) | |
| 125 } | |
| 126 | |
| 127 // computeFlags parses the raw data to set both the Debug and Release flags. | |
| 128 func (r *FuzzResult) computeFlags(category string) { | |
| 129 r.Debug.Flags = parseAll(category, &r.Debug) | |
| 130 r.Release.Flags = parseAll(category, &r.Release) | |
| 131 } | |
| 132 | |
| 133 // parseAll looks at the three input files and parses the results, based on the
category. The | |
| 134 // category allows for specialized flags, like SKPICTURE_DuringRendering. | |
| 135 func parseAll(category string, data *BuildData) FuzzFlag { | |
| 136 f := FuzzFlag(0) | |
| 137 // Check for SKAbort message | |
| 138 if strings.Contains(data.Asan, "fatal error") { | |
| 139 f |= ASANCrashed | |
| 140 f |= SKAbortHit | |
| 141 if data.StackTrace.IsEmpty() { | |
| 142 data.StackTrace = extractSkAbortTrace(data.StdErr) | |
| 143 } | |
| 144 } | |
| 145 if strings.Contains(data.StdErr, "fatal error") { | |
| 146 f |= ClangCrashed | |
| 147 f |= SKAbortHit | |
| 148 if data.StackTrace.IsEmpty() { | |
| 149 data.StackTrace = extractSkAbortTrace(data.StdErr) | |
| 150 } | |
| 151 } | |
| 152 // If no sk abort message and no evidence of crashes, we either terminat
ed gracefully or | |
| 153 // timed out. | |
| 154 if f == 0 && !asanCrashed(data.Asan) && !clangDumped(data.Dump) { | |
| 155 if strings.Contains(data.Asan, "[terminated]") && strings.Contai
ns(data.StdErr, "[terminated]") { | |
| 156 return TerminatedGracefully | |
| 157 } | |
| 158 return TimedOut | |
| 159 } | |
| 160 | |
| 161 // Look for clues from the various dumps. | |
| 162 f |= parseAsan(category, data.Asan) | |
| 163 f |= parseCatchsegv(category, data.Dump, data.StdErr) | |
| 164 if f == 0 { | |
| 165 // I don't know what this means (yet). | |
| 166 return Other | |
| 167 } | |
| 168 if data.StackTrace.IsEmpty() { | |
| 169 f |= NoStackTrace | |
| 170 } | |
| 171 return f | |
| 172 } | |
| 173 | |
| 174 // parseAsan returns the flags discovered while looking through the AddressSanit
izer output. This | |
| 175 // includes things like ASAN_GlobalBufferOverflow. | |
| 176 func parseAsan(category, asan string) FuzzFlag { | |
| 177 f := FuzzFlag(0) | |
| 178 if !asanCrashed(asan) { | |
| 179 return f | |
| 180 } | |
| 181 f |= ASANCrashed | |
| 182 if strings.Contains(asan, "failed assertion") { | |
| 183 f |= AssertionViolated | |
| 184 } | |
| 185 if strings.Contains(asan, "global-buffer-overflow") { | |
| 186 f |= ASAN_GlobalBufferOverflow | |
| 187 } | |
| 188 if strings.Contains(asan, "heap-buffer-overflow") { | |
| 189 f |= ASAN_HeapBufferOverflow | |
| 190 } | |
| 191 if strings.Contains(asan, "stack-buffer-overflow") { | |
| 192 f |= ASAN_StackBufferOverflow | |
| 193 } | |
| 194 if strings.Contains(asan, "heap-use-after-free") { | |
| 195 f |= ASAN_HeapUseAfterFree | |
| 196 } | |
| 197 if strings.Contains(asan, "AddressSanitizer failed to allocate") { | |
| 198 f |= BadAlloc | |
| 199 } | |
| 200 | |
| 201 // Split off the stderr that happened before the crash. | |
| 202 errs := strings.Split(asan, "=================") | |
| 203 if len(errs) > 0 { | |
| 204 err := errs[0] | |
| 205 if category == "skpicture" && strings.Contains(err, "Rendering")
{ | |
| 206 f |= SKPICTURE_DuringRendering | |
| 207 } | |
| 208 } | |
| 209 return f | |
| 210 } | |
| 211 | |
| 212 // asanCrashed returns true if the asan output is consistent with a crash. | |
| 213 func asanCrashed(asan string) bool { | |
| 214 return strings.Contains(asan, "ERROR: AddressSanitizer:") | |
| 215 } | |
| 216 | |
| 217 // parseAsan returns the flags discovered while looking through the Clang dump a
nd standard error. | |
| 218 // This includes things like | |
| 219 func parseCatchsegv(category, dump, err string) FuzzFlag { | |
| 220 f := FuzzFlag(0) | |
| 221 if !clangDumped(dump) && strings.Contains(err, "[terminated]") { | |
| 222 return f | |
| 223 } | |
| 224 f |= ClangCrashed | |
| 225 if strings.Contains(err, "failed assertion") { | |
| 226 f |= AssertionViolated | |
| 227 } | |
| 228 if category == "skpicture" && strings.Contains(err, "Rendering") { | |
| 229 f |= SKPICTURE_DuringRendering | |
| 230 } | |
| 231 if strings.Contains(err, "std::bad_alloc") { | |
| 232 f |= BadAlloc | |
| 233 } | |
| 234 return f | |
| 235 } | |
| 236 | |
| 237 // clangDumped returns true if the clang output is consistent with a crash, that
is, non empty. | |
| 238 func clangDumped(dump string) bool { | |
| 239 return len(dump) != 0 | |
| 240 } | |
| 241 | |
| 242 var skAbortStackTraceLine = regexp.MustCompile(`(?:\.\./)+(?P<package>(?:\w+/)+)
(?P<file>.+):(?P<line>\d+): fatal error`) | |
| 243 | |
| 244 // extractSkAbortTrace looks for the fatal error string indicative of the SKAbor
t termination | |
| 245 // and tries to pull out the stacktrace frame on which it happened. | |
| 246 func extractSkAbortTrace(err string) StackTrace { | |
| 247 st := StackTrace{} | |
| 248 if match := skAbortStackTraceLine.FindStringSubmatch(err); match != nil
{ | |
| 249 st.Frames = append(st.Frames, FullStackFrame(match[1], match[2],
common.UNKNOWN_FUNCTION, safeParseInt(match[3]))) | |
| 250 } | |
| 251 return st | |
| 252 } | |
| OLD | NEW |