mirror of
https://github.com/anchore/syft.git
synced 2026-07-05 02:28:25 +02:00
ci: ci
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
This commit is contained in:
parent
46f9974fec
commit
9b6fc99727
@ -82,11 +82,12 @@ func (c *goBinaryCataloger) recordStdlibSymbols(coord file.Coordinates, symbols
|
||||
c.stdlibSymbols[coord] = slices.Compact(merged)
|
||||
}
|
||||
|
||||
// stdlibSymbolsFor returns the standard-library symbols recorded for a binary location.
|
||||
// stdlibSymbolsFor returns the standard-library symbols recorded for a binary location. It returns a copy
|
||||
// so callers cannot alias (and later mutate or race on) the map's internal slice.
|
||||
func (c *goBinaryCataloger) stdlibSymbolsFor(coord file.Coordinates) []string {
|
||||
c.stdlibSymbolsMu.Lock()
|
||||
defer c.stdlibSymbolsMu.Unlock()
|
||||
return c.stdlibSymbols[coord]
|
||||
return slices.Clone(c.stdlibSymbols[coord])
|
||||
}
|
||||
|
||||
// parseGoBinary catalogs packages found in the "buildinfo" section of a binary built by the go compiler.
|
||||
@ -429,7 +430,7 @@ func getExperimentsFromVersion(version string) (string, []string) {
|
||||
version, rest, ok := strings.Cut(version, " ")
|
||||
if ok {
|
||||
// Assume they may add more non-version chunks in the future, so only look for "X:".
|
||||
for _, chunk := range strings.Split(rest, " ") {
|
||||
for chunk := range strings.SplitSeq(rest, " ") {
|
||||
if strings.HasPrefix(rest, "X:") {
|
||||
csv := strings.TrimPrefix(chunk, "X:")
|
||||
experiments = append(experiments, strings.Split(csv, ",")...)
|
||||
|
||||
@ -47,7 +47,7 @@ func getSymbols(r io.ReaderAt) (syms []binarySymbol, err error) {
|
||||
|
||||
seen := make(map[string]struct{})
|
||||
for _, fn := range table.Funcs {
|
||||
if fn.Sym == nil {
|
||||
if fn.Sym == nil || isCompilerGeneratedName(fn.Name) {
|
||||
continue
|
||||
}
|
||||
seen[fn.Name] = struct{}{}
|
||||
@ -79,8 +79,11 @@ func getSymbols(r io.ReaderAt) (syms []binarySymbol, err error) {
|
||||
// packagePathFromSymbolName derives the owning package import path from a fully qualified symbol name.
|
||||
// The package path is everything up to the first "." that follows the final "/" — e.g.
|
||||
// "path/filepath.IsLocal" -> "path/filepath" and "golang.org/x/net/html.(*Tokenizer).Next" ->
|
||||
// "golang.org/x/net/html". Returns "" when the name has no package-qualifying dot.
|
||||
// "golang.org/x/net/html". Returns "" when the name has no package-qualifying dot or is compiler-generated.
|
||||
func packagePathFromSymbolName(name string) string {
|
||||
if isCompilerGeneratedName(name) {
|
||||
return ""
|
||||
}
|
||||
slash := strings.LastIndex(name, "/")
|
||||
dot := strings.IndexByte(name[slash+1:], '.')
|
||||
if dot < 0 {
|
||||
@ -89,6 +92,15 @@ func packagePathFromSymbolName(name string) string {
|
||||
return name[:slash+1+dot]
|
||||
}
|
||||
|
||||
// isCompilerGeneratedName reports whether a symbol name was synthesized by the compiler or linker rather
|
||||
// than declared in Go source. These names use ':' or '..' (e.g. "type:.eq.*", "type..hash.*",
|
||||
// "go:string.*") — byte sequences that never appear in a real Go import path or identifier — so they
|
||||
// belong to no package and are dropped rather than mis-attributed (e.g. bucketed under a bogus "type"
|
||||
// stdlib package).
|
||||
func isCompilerGeneratedName(name string) bool {
|
||||
return strings.Contains(name, ":") || strings.Contains(name, "..")
|
||||
}
|
||||
|
||||
// funcNameTable returns every function name recorded in the pclntab's funcname table, including the
|
||||
// names of inlined functions that debug/gosym does not expose. It parses the pclntab header for the
|
||||
// Go 1.16+ layouts; on any unrecognized layout or out-of-bounds offset it returns nil (fail-soft), so
|
||||
@ -143,7 +155,7 @@ func funcNameTable(pclntab []byte) []string {
|
||||
}
|
||||
|
||||
var names []string
|
||||
for _, raw := range bytes.Split(pclntab[start:end], []byte{0}) {
|
||||
for raw := range bytes.SplitSeq(pclntab[start:end], []byte{0}) {
|
||||
if len(raw) == 0 {
|
||||
continue
|
||||
}
|
||||
@ -264,9 +276,6 @@ func moduleSymbols(symbols []binarySymbol, main *debug.Module, deps []*debug.Mod
|
||||
// "net/http", "runtime", "internal/abi"), which distinguishes it from module paths like
|
||||
// "github.com/foo/bar" whose leading element is a domain name.
|
||||
func isStandardImportPath(path string) bool {
|
||||
first := path
|
||||
if i := strings.Index(path, "/"); i >= 0 {
|
||||
first = path[:i]
|
||||
}
|
||||
first, _, _ := strings.Cut(path, "/")
|
||||
return first != "" && !strings.Contains(first, ".")
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"debug/gosym"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
@ -124,4 +125,65 @@ func Test_getSymbols(t *testing.T) {
|
||||
}
|
||||
assert.True(t, foundRuntime, "expected to find runtime.main symbol")
|
||||
assert.True(t, foundTesting, "expected to find testing.tRunner symbol")
|
||||
|
||||
// the recovery loop relies on packagePathFromSymbolName, so confirm at least one recovered name is
|
||||
// present that debug/gosym's table.Funcs does not surface directly (i.e. an inlined function).
|
||||
require.True(t, hasInlinedOnlySymbol(t, exe, symbols), "expected to recover at least one inlined-only symbol")
|
||||
}
|
||||
|
||||
// hasInlinedOnlySymbol reports whether syms contains a function name that is absent from the raw
|
||||
// debug/gosym function table for the same binary — i.e. a name that could only have come from the
|
||||
// funcname-table recovery path.
|
||||
func hasInlinedOnlySymbol(t *testing.T, exe string, syms []binarySymbol) bool {
|
||||
t.Helper()
|
||||
|
||||
f, err := os.Open(exe)
|
||||
require.NoError(t, err)
|
||||
defer f.Close()
|
||||
|
||||
pclntab, textStart, err := readPclntab(f)
|
||||
require.NoError(t, err)
|
||||
|
||||
table, err := gosym.NewTable(nil, gosym.NewLineTable(pclntab, textStart))
|
||||
require.NoError(t, err)
|
||||
|
||||
gosymNames := make(map[string]struct{}, len(table.Funcs))
|
||||
for _, fn := range table.Funcs {
|
||||
gosymNames[fn.Name] = struct{}{}
|
||||
}
|
||||
|
||||
for _, sym := range syms {
|
||||
if _, ok := gosymNames[sym.name]; !ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func Test_packagePathFromSymbolName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
expected string
|
||||
}{
|
||||
{"path/filepath.IsLocal", "path/filepath"},
|
||||
// pointer-receiver method
|
||||
{"golang.org/x/net/html.(*Tokenizer).Next", "golang.org/x/net/html"},
|
||||
// versioned (major-version-suffixed) module path
|
||||
{"github.com/foo/bar/v2.Parse", "github.com/foo/bar/v2"},
|
||||
{"github.com/foo/bar/v2.(*Client).Do", "github.com/foo/bar/v2"},
|
||||
{"github.com/foo/bar.Parse.func1", "github.com/foo/bar"},
|
||||
{"main.main", "main"},
|
||||
{"runtime.gcBgMarkWorker", "runtime"},
|
||||
// no package-qualifying dot
|
||||
{"runtime", ""},
|
||||
// compiler/linker-generated symbols belong to no package
|
||||
{"type:.eq.[]string", ""},
|
||||
{"type..hash.runtime._type", ""},
|
||||
{"go:string.\"foo\"", ""},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, packagePathFromSymbolName(test.name))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user