blob: f8b0caa5301aba61cfde431b82b7b10d13de76d9 [file] [log] [blame]
package data
import (
"bufio"
"bytes"
"fmt"
"regexp"
"strings"
"go.skia.org/infra/fuzzer/go/common"
"go.skia.org/infra/go/util"
)
type StackTrace struct {
Frames []StackTraceFrame `json:"frames"`
}
type StackTraceFrame struct {
PackageName string `json:"packageName"`
FileName string `json:"fileName"`
LineNumber int `json:"lineNumber"`
FunctionName string `json:"functionName"`
}
// The `?:` at the beginning of the groups prevent them from being captured
// \1 is the "package", \2 is the file name, \3 is the line number, \4 is the function symbol string
var segvStackTraceLine = regexp.MustCompile(`(?:\.\./)+(?P<package>(?:\w+/)+)(?P<file>.+):(?P<line>\d+).*\(_(?P<symbol>.*)\)`)
// Occasionally, unsymbolized outputs sneak through. We give it a best effort to parse.
var segvStackTraceLineUnsymbolized = regexp.MustCompile(`\(_(?P<symbol>Z.*)\)`)
// Sometimes, we crash in assembly code that doesn't have a file attached.
var segvAssemblyStackTraceLine = regexp.MustCompile(`\:\?\((?P<function>.+)\)\[`)
// parseCatchsegvStackTrace takes the contents of a dump file of a catchsegv run, and returns the
// parsed stacktrace
func parseCatchsegvStackTrace(contents string) StackTrace {
r := bytes.NewBufferString(contents)
scan := bufio.NewScanner(r)
hasBegun := false
frames := make([]StackTraceFrame, 0, 5)
for scan.Scan() {
line := scan.Text()
if strings.Contains(line, "Backtrace") {
hasBegun = true
}
if strings.Contains(line, "Memory map") {
break
}
if !hasBegun {
continue
}
if match := segvStackTraceLine.FindStringSubmatch(line); match != nil {
// match[0] is the entire matched portion, [1] is the "package", [2] is the file name,
// [3] is the line number and [4] is the encoded function symbol string.
newFrame := FullStackFrame(match[1], match[2], catchsegvFunctionName(match[4]), util.SafeAtoi(match[3]))
frames = append(frames, newFrame)
} else if match := segvStackTraceLineUnsymbolized.FindStringSubmatch(line); match != nil {
newFrame := FullStackFrame(common.UNSYMBOLIZED_RESULT, common.UNSYMBOLIZED_RESULT, catchsegvFunctionName(match[1]), -1)
frames = append(frames, newFrame)
} else if match := segvAssemblyStackTraceLine.FindStringSubmatch(line); match != nil {
// match[1] is the function name.
newFrame := FullStackFrame("", common.ASSEMBLY_CODE_FILE, match[1], common.UNKNOWN_LINE)
frames = append(frames, newFrame)
}
}
return StackTrace{Frames: frames}
}
var staticStart = regexp.MustCompile(`^ZL(\d+)`)
var nonstaticStart = regexp.MustCompile(`^Z(\d+)`)
var methodStart = regexp.MustCompile(`^(ZNK?)(\d+)`)
var methodName = regexp.MustCompile(`^(\d+)`)
// catchsegvFunctionName parses the symbol string from a stacktrace to
// get the name of the related function.
//TODO(kjlubick) parse arguments if that helps the readability of the stacktraces
// Here are some examples of symbol strings and what the various chars mean:
// (Parameters are after the names, but are unparsed as of yet)
// ZL12convert_to_8 -> ZL12 -> static function 12 long "convert_to_8"
// Z9tool_mainiPPc -> non-static function, 9 long "tool_main"
// ZN14SkBmpMaskCodec10decodeRows -> ZN14 -> type is 14 long, method name is 10 long "decodeRows"
// ZNK2DM6SKPSrc4drawEP8SkCanvas -> ZNK2 -> type is 2 long (method is konstant) "DM" ->
// 6 -> Sub type is 6 long "SKPSrc" -> 4 -> method is 4 long "draw"
// (since there is no number directly after the 3rd step, we have found the param boundary).
func catchsegvFunctionName(s string) string {
if match := methodStart.FindStringSubmatch(s); match != nil {
length := util.SafeAtoi(match[2])
// ZNK? is 2-3 chars, so slice (num letters + num digits + number of spaces) chars off
// the beginning.
if len(match[1])+len(match[2])+length >= len(s) {
// This is a malformed stacktrace, somehow
return common.UNKNOWN_FUNCTION
}
s = s[len(match[1])+len(match[2])+length:]
f := ""
// We look at the beginning of our trimmed string for numbers.
// if there are numbers, we pull off a piece of the name and scan again.
// If there is more than one piece, we separate it with ::, because it is a nested type
// or enum thing.
for match := methodName.FindStringSubmatch(s); match != nil; match = methodName.FindStringSubmatch(s) {
if f != "" {
f += "::"
}
length = util.SafeAtoi(match[1])
start := len(match[1])
if start >= len(s) || start+length >= len(s) {
// This is a malformed stacktrace, somehow
return common.UNKNOWN_FUNCTION
}
f += s[start : start+length]
s = s[start+length:]
}
return f
}
if match := staticStart.FindStringSubmatch(s); match != nil {
length := util.SafeAtoi(match[1])
// ZL is 2 chars, so advance 2 spaces + how many digits there are
start := 2 + len(match[1])
if start >= len(s) || start+length >= len(s) {
// This is a malformed stacktrace, somehow
return common.UNKNOWN_FUNCTION
}
return s[start : start+length]
}
if match := nonstaticStart.FindStringSubmatch(s); match != nil {
length := util.SafeAtoi(match[1])
// Z is 1 char, so advance 1 space + how many digits there are
start := 1 + len(match[1])
if start >= len(s) || start+length >= len(s) {
// This is a malformed stacktrace, somehow
return common.UNKNOWN_FUNCTION
}
return s[start : start+length]
}
return common.UNKNOWN_FUNCTION
}
// The `?:` at the beginning of the groups prevent them from being captured
// \1 is the (hopefully symbolized) function name, \2 is the "package", \3 is the file name,
// \4 is the line number
var asanStackTraceLine = regexp.MustCompile(`in (?P<function>[a-zA-Z0-9_:]+).*(?:\.\./)+(?P<package>(?:\w+/)+)(?P<file>.+?):(?P<line>\d+)`)
var asanAssemblyStackTraceLine = regexp.MustCompile(`in (?P<function>[a-zA-Z0-9_:]+) \(`)
// parseCatchsegvStackTrace takes the output of an AddressSanitizer crash, and returns the parsed
// StackTrace, if it is able to find one. If the result is not symbolized, this will return
// an empty StackTrace.
func parseASANStackTrace(contents string) StackTrace {
r := bytes.NewBufferString(contents)
scan := bufio.NewScanner(r)
frames := make([]StackTraceFrame, 0, 5)
hasBegun := false
for scan.Scan() {
line := scan.Text()
if strings.Contains(line, "ERROR: AddressSanitizer:") {
hasBegun = true
continue
}
if hasBegun && line == "" {
break
}
if !hasBegun {
continue
}
line = strings.Replace(line, "(anonymous namespace)::", "", -1)
if match := asanStackTraceLine.FindStringSubmatch(line); match != nil {
// match[0] is the entire matched portion, [1] is the function name [2] is the
// "package", [3] is the file name, [4] is the line number
newFrame := FullStackFrame(match[2], match[3], match[1], util.SafeAtoi(match[4]))
frames = append(frames, newFrame)
} else if match := asanAssemblyStackTraceLine.FindStringSubmatch(line); match != nil {
// match[1] is the function name.
newFrame := FullStackFrame("", common.ASSEMBLY_CODE_FILE, match[1], common.UNKNOWN_LINE)
frames = append(frames, newFrame)
}
}
return StackTrace{Frames: frames}
}
var asanSummaryLine = regexp.MustCompile(`SUMMARY.*(?:\.\./)+(?P<package>(?:\w+/)+)(?P<file>.+?):(?P<line>\d+) ?(?P<function>[a-zA-Z0-9_:]+)?`)
func parseASANSummary(contents string) StackTrace {
r := bytes.NewBufferString(contents)
scan := bufio.NewScanner(r)
for scan.Scan() {
line := scan.Text()
line = strings.Replace(line, "(anonymous namespace)::", "", -1)
if match := asanSummaryLine.FindStringSubmatch(line); match != nil {
// match[0] is the entire matched portion, [1] is the
// "package", [2] is the file name, [3] is the line number [4] is the function name
f := common.UNKNOWN_FUNCTION
if len(match) == 5 {
f = match[4]
}
if f == "" {
f = common.UNKNOWN_FUNCTION
}
newFrame := FullStackFrame(match[1], match[2], f, util.SafeAtoi(match[3]))
return StackTrace{Frames: []StackTraceFrame{newFrame}}
}
}
return StackTrace{}
}
// FullStackFrame creates a StackTraceFrame with all components
func FullStackFrame(packageName, fileName, functionName string, lineNumber int) StackTraceFrame {
return StackTraceFrame{
PackageName: packageName,
FileName: fileName,
LineNumber: lineNumber,
FunctionName: functionName,
}
}
func (f *StackTraceFrame) String() string {
return fmt.Sprintf("%s%s:%d %s", f.PackageName, f.FileName, f.LineNumber, f.FunctionName)
}
func (st *StackTrace) String() string {
s := fmt.Sprintf("StackTrace with %d frames:\n", len(st.Frames))
for _, f := range st.Frames {
s += fmt.Sprintf("\t%s\n", f.String())
}
return s
}
func (st *StackTrace) IsEmpty() bool {
return len(st.Frames) == 0
}