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)
|
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 {
|
func (c *goBinaryCataloger) stdlibSymbolsFor(coord file.Coordinates) []string {
|
||||||
c.stdlibSymbolsMu.Lock()
|
c.stdlibSymbolsMu.Lock()
|
||||||
defer c.stdlibSymbolsMu.Unlock()
|
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.
|
// 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, " ")
|
version, rest, ok := strings.Cut(version, " ")
|
||||||
if ok {
|
if ok {
|
||||||
// Assume they may add more non-version chunks in the future, so only look for "X:".
|
// 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:") {
|
if strings.HasPrefix(rest, "X:") {
|
||||||
csv := strings.TrimPrefix(chunk, "X:")
|
csv := strings.TrimPrefix(chunk, "X:")
|
||||||
experiments = append(experiments, strings.Split(csv, ",")...)
|
experiments = append(experiments, strings.Split(csv, ",")...)
|
||||||
|
|||||||
@ -47,7 +47,7 @@ func getSymbols(r io.ReaderAt) (syms []binarySymbol, err error) {
|
|||||||
|
|
||||||
seen := make(map[string]struct{})
|
seen := make(map[string]struct{})
|
||||||
for _, fn := range table.Funcs {
|
for _, fn := range table.Funcs {
|
||||||
if fn.Sym == nil {
|
if fn.Sym == nil || isCompilerGeneratedName(fn.Name) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seen[fn.Name] = struct{}{}
|
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.
|
// 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.
|
// 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" ->
|
// "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 {
|
func packagePathFromSymbolName(name string) string {
|
||||||
|
if isCompilerGeneratedName(name) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
slash := strings.LastIndex(name, "/")
|
slash := strings.LastIndex(name, "/")
|
||||||
dot := strings.IndexByte(name[slash+1:], '.')
|
dot := strings.IndexByte(name[slash+1:], '.')
|
||||||
if dot < 0 {
|
if dot < 0 {
|
||||||
@ -89,6 +92,15 @@ func packagePathFromSymbolName(name string) string {
|
|||||||
return name[:slash+1+dot]
|
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
|
// 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
|
// 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
|
// 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
|
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 {
|
if len(raw) == 0 {
|
||||||
continue
|
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
|
// "net/http", "runtime", "internal/abi"), which distinguishes it from module paths like
|
||||||
// "github.com/foo/bar" whose leading element is a domain name.
|
// "github.com/foo/bar" whose leading element is a domain name.
|
||||||
func isStandardImportPath(path string) bool {
|
func isStandardImportPath(path string) bool {
|
||||||
first := path
|
first, _, _ := strings.Cut(path, "/")
|
||||||
if i := strings.Index(path, "/"); i >= 0 {
|
|
||||||
first = path[:i]
|
|
||||||
}
|
|
||||||
return first != "" && !strings.Contains(first, ".")
|
return first != "" && !strings.Contains(first, ".")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package golang
|
package golang
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"debug/gosym"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/debug"
|
"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, foundRuntime, "expected to find runtime.main symbol")
|
||||||
assert.True(t, foundTesting, "expected to find testing.tRunner 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