cairn/internal/fingerprint/normalize.go

107 lines
1.8 KiB
Go

package fingerprint
import (
"path/filepath"
"regexp"
"strings"
)
const maxFrames = 8
var (
hexAddrRe = regexp.MustCompile(`0x[0-9a-fA-F]+`)
templateParamRe = regexp.MustCompile(`<[^>]*>`)
abiTagRe = regexp.MustCompile(`\[abi:[^\]]*\]`)
)
// runtimePrefixes are function prefixes for runtime/library frames to filter out.
var runtimePrefixes = []string{
"__libc_",
"__GI_",
"_start",
"__clone",
"start_thread",
"__pthread_",
"__sigaction",
"_dl_",
"__tls_",
// glibc allocator internals
"__libc_malloc",
"__libc_free",
"malloc",
"free",
"realloc",
"calloc",
// ASan runtime
"__asan_",
"__sanitizer_",
"__interceptor_",
"__interception::",
// Zig std runtime
"std.debug.",
"std.start.",
"std.os.linux.",
"posixCallNative",
}
// Normalize applies stability-oriented transformations to parsed frames.
func Normalize(frames []Frame) []NormalizedFrame {
var result []NormalizedFrame
for _, f := range frames {
// Skip inline frames.
if f.Inline {
continue
}
fn := f.Function
// Skip runtime/library frames.
if isRuntimeFrame(fn) {
continue
}
// Strip hex addresses.
fn = hexAddrRe.ReplaceAllString(fn, "")
// Strip C++ template parameters.
fn = templateParamRe.ReplaceAllString(fn, "<>")
// Strip ABI tags.
fn = abiTagRe.ReplaceAllString(fn, "")
// Clean up whitespace.
fn = strings.TrimSpace(fn)
// Strip paths to just filename.
file := f.File
if file != "" {
file = filepath.Base(file)
}
if fn == "" {
continue
}
result = append(result, NormalizedFrame{
Function: fn,
File: file,
})
if len(result) >= maxFrames {
break
}
}
return result
}
func isRuntimeFrame(fn string) bool {
for _, prefix := range runtimePrefixes {
if strings.HasPrefix(fn, prefix) {
return true
}
}
return false
}