diff --git a/cmd/syft/internal/options/golang.go b/cmd/syft/internal/options/golang.go index 688ec7b08..7d13d289d 100644 --- a/cmd/syft/internal/options/golang.go +++ b/cmd/syft/internal/options/golang.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/anchore/clio" + "github.com/anchore/syft/syft/cataloging" "github.com/anchore/syft/syft/pkg/cataloger/golang" ) @@ -18,7 +19,7 @@ type golangConfig struct { NoProxy string `json:"no-proxy" yaml:"no-proxy" mapstructure:"no-proxy"` MainModuleVersion golangMainModuleVersionConfig `json:"main-module-version" yaml:"main-module-version" mapstructure:"main-module-version"` UsePackagesLib *bool `json:"use-packages-lib" yaml:"use-packages-lib" mapstructure:"use-packages-lib"` - CaptureSymbols golang.SymbolScope `json:"capture-symbols" yaml:"capture-symbols" mapstructure:"capture-symbols"` + CaptureSymbols cataloging.SymbolScope `json:"capture-symbols" yaml:"capture-symbols" mapstructure:"capture-symbols"` } var _ interface { diff --git a/cmd/syft/internal/options/golang_test.go b/cmd/syft/internal/options/golang_test.go index bb64f1da1..8a4b7e997 100644 --- a/cmd/syft/internal/options/golang_test.go +++ b/cmd/syft/internal/options/golang_test.go @@ -5,30 +5,30 @@ import ( "github.com/stretchr/testify/assert" - "github.com/anchore/syft/syft/pkg/cataloger/golang" + "github.com/anchore/syft/syft/cataloging" ) func Test_golangConfig_PostLoad(t *testing.T) { tests := []struct { name string cfg golangConfig - expected golang.SymbolScope + expected cataloging.SymbolScope wantErr assert.ErrorAssertionFunc }{ { name: "normalize all", cfg: golangConfig{CaptureSymbols: "all"}, - expected: golang.SymbolScopeAll, + expected: cataloging.SymbolScopeAll, }, { name: "normalize stdlib", cfg: golangConfig{CaptureSymbols: "stdlib"}, - expected: golang.SymbolScopeStdlib, + expected: cataloging.SymbolScopeStdlib, }, { name: "empty defaults to none", cfg: golangConfig{CaptureSymbols: ""}, - expected: golang.SymbolScopeNone, + expected: cataloging.SymbolScopeNone, }, { name: "error on invalid value", diff --git a/internal/constants.go b/internal/constants.go index 21046590c..ba4233eb5 100644 --- a/internal/constants.go +++ b/internal/constants.go @@ -10,6 +10,7 @@ const ( // 16.1.1 - correct elf package osCpe field according to the document of systemd (also add appCpe field) // 16.1.2 - placeholder for 16.1.2 changelog // 16.1.3 - add GGUFFileParts to GGUFFileHeader metadata + // 16.1.4 - add BunLockEntry metadata type for bun.lock support // 16.1.5 - add DenoLockEntry and DenoRemoteLockEntry metadata types for deno.lock support // 16.1.6 - add Dependencies to ElixirMixLockEntry metadata // 16.1.7 - add AppleAppBundleEntry metadata type for the apple app bundle cataloger diff --git a/schema/json/schema-16.1.8.json b/schema/json/schema-16.1.8.json index 13fd9d488..d2a0108d3 100644 --- a/schema/json/schema-16.1.8.json +++ b/schema/json/schema-16.1.8.json @@ -1659,7 +1659,7 @@ "type": "string" }, "type": "array", - "description": "Symbols are the fully qualified function symbols from this module that are compiled into the binary\n(e.g., \"github.com/foo/bar.(*Type).Method\"), extracted from the binary symbol table (pclntab).\nOnly captured when the golang cataloger is configured to capture symbols." + "description": "Symbols are the fully qualified function symbols from this module that are compiled into the binary\n(e.g., \"github.com/foo/bar.(*Type).Method\"), extracted from the binary symbol table (pclntab).\nPopulated only when the golang cataloger's capture-symbols scope covers this package: the \"all\" scope\npopulates every module package plus the synthetic stdlib package, while the \"stdlib\" scope populates\nonly the stdlib package." } }, "type": "object", diff --git a/schema/json/schema-latest.json b/schema/json/schema-latest.json index 13fd9d488..d2a0108d3 100644 --- a/schema/json/schema-latest.json +++ b/schema/json/schema-latest.json @@ -1659,7 +1659,7 @@ "type": "string" }, "type": "array", - "description": "Symbols are the fully qualified function symbols from this module that are compiled into the binary\n(e.g., \"github.com/foo/bar.(*Type).Method\"), extracted from the binary symbol table (pclntab).\nOnly captured when the golang cataloger is configured to capture symbols." + "description": "Symbols are the fully qualified function symbols from this module that are compiled into the binary\n(e.g., \"github.com/foo/bar.(*Type).Method\"), extracted from the binary symbol table (pclntab).\nPopulated only when the golang cataloger's capture-symbols scope covers this package: the \"all\" scope\npopulates every module package plus the synthetic stdlib package, while the \"stdlib\" scope populates\nonly the stdlib package." } }, "type": "object", diff --git a/syft/cataloging/symbols.go b/syft/cataloging/symbols.go new file mode 100644 index 000000000..77f41050c --- /dev/null +++ b/syft/cataloging/symbols.go @@ -0,0 +1,31 @@ +package cataloging + +import "strings" + +// SymbolScope controls which packages get function symbols (from a binary's symbol table) attached to their metadata. +type SymbolScope string + +const ( + // SymbolScopeNone disables symbol capture entirely. + SymbolScopeNone SymbolScope = "none" + + // SymbolScopeStdlib captures symbols only for the synthetic "stdlib" package, leaving module packages without symbols. + SymbolScopeStdlib SymbolScope = "stdlib" + + // SymbolScopeAll captures symbols for all module packages as well as the synthetic "stdlib" package. + SymbolScopeAll SymbolScope = "all" +) + +// Parse normalizes a SymbolScope, treating an empty (unset) value as SymbolScopeNone. It returns an empty +// SymbolScope to signal an unrecognized value, which callers validate against. +func (s SymbolScope) Parse() SymbolScope { + switch strings.ToLower(strings.TrimSpace(string(s))) { + case string(SymbolScopeAll): + return SymbolScopeAll + case string(SymbolScopeStdlib): + return SymbolScopeStdlib + case string(SymbolScopeNone), "": + return SymbolScopeNone + } + return "" +} diff --git a/syft/cataloging/symbols_test.go b/syft/cataloging/symbols_test.go new file mode 100644 index 000000000..b610e7d73 --- /dev/null +++ b/syft/cataloging/symbols_test.go @@ -0,0 +1,30 @@ +package cataloging + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_SymbolScope_Parse(t *testing.T) { + tests := []struct { + input string + expected SymbolScope + }{ + {"all", SymbolScopeAll}, + {"ALL", SymbolScopeAll}, + {" all ", SymbolScopeAll}, + {"stdlib", SymbolScopeStdlib}, + {"Stdlib", SymbolScopeStdlib}, + {"none", SymbolScopeNone}, + {"", SymbolScopeNone}, + {"true", ""}, + {"false", ""}, + {"bogus", ""}, + } + for _, test := range tests { + t.Run(test.input, func(t *testing.T) { + assert.Equal(t, test.expected, SymbolScope(test.input).Parse()) + }) + } +} diff --git a/syft/pkg/cataloger/golang/config.go b/syft/pkg/cataloger/golang/config.go index 11d1f8bad..228cd67b1 100644 --- a/syft/pkg/cataloger/golang/config.go +++ b/syft/pkg/cataloger/golang/config.go @@ -7,6 +7,7 @@ import ( "github.com/anchore/go-homedir" "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/cataloging" ) const ( @@ -18,34 +19,6 @@ var ( directProxiesOnly = []string{directProxyOnly} ) -// SymbolScope controls which packages get function symbols (from the binary pclntab) attached to their metadata. -type SymbolScope string - -const ( - // SymbolScopeNone disables symbol capture entirely. - SymbolScopeNone SymbolScope = "none" - - // SymbolScopeStdlib captures symbols only for the synthetic "stdlib" package, leaving module packages without symbols. - SymbolScopeStdlib SymbolScope = "stdlib" - - // SymbolScopeAll captures symbols for all module packages as well as the synthetic "stdlib" package. - SymbolScopeAll SymbolScope = "all" -) - -// Parse normalizes a SymbolScope, treating an empty (unset) value as SymbolScopeNone. It returns an empty -// SymbolScope to signal an unrecognized value, which callers validate against. -func (s SymbolScope) Parse() SymbolScope { - switch strings.ToLower(strings.TrimSpace(string(s))) { - case string(SymbolScopeAll): - return SymbolScopeAll - case string(SymbolScopeStdlib): - return SymbolScopeStdlib - case string(SymbolScopeNone), "": - return SymbolScopeNone - } - return "" -} - type CatalogerConfig struct { // SearchLocalModCacheLicenses enables searching for go package licenses in the local GOPATH mod cache. // app-config: golang.search-local-mod-cache-licenses @@ -80,7 +53,7 @@ type CatalogerConfig struct { // CaptureSymbols controls extracting function symbols from the binary symbol table (pclntab). Valid values are // "none" (disabled), "stdlib" (only the synthetic stdlib package), and "all" (all module packages plus stdlib). // app-config: golang.capture-symbols - CaptureSymbols SymbolScope `yaml:"capture-symbols" json:"capture-symbols" mapstructure:"capture-symbols"` + CaptureSymbols cataloging.SymbolScope `yaml:"capture-symbols" json:"capture-symbols" mapstructure:"capture-symbols"` // Whether to use the golang.org/x/tools/go/packages, which executes golang tooling found on the path in addition to potential network access UsePackagesLib bool `json:"use-packages-lib" yaml:"use-packages-lib" mapstructure:"use-packages-lib"` @@ -109,7 +82,7 @@ func DefaultCatalogerConfig() CatalogerConfig { UsePackagesLib: true, MainModuleVersion: DefaultMainModuleVersionConfig(), LocalModCacheDir: defaultGoModDir(), - CaptureSymbols: SymbolScopeNone, + CaptureSymbols: cataloging.SymbolScopeNone, } // first process the proxy settings @@ -218,7 +191,7 @@ func (g CatalogerConfig) WithMainModuleVersion(input MainModuleVersionConfig) Ca return g } -func (g CatalogerConfig) WithCaptureSymbols(input SymbolScope) CatalogerConfig { +func (g CatalogerConfig) WithCaptureSymbols(input cataloging.SymbolScope) CatalogerConfig { g.CaptureSymbols = input return g } diff --git a/syft/pkg/cataloger/golang/config_test.go b/syft/pkg/cataloger/golang/config_test.go index e20f9d47b..1b16a190a 100644 --- a/syft/pkg/cataloger/golang/config_test.go +++ b/syft/pkg/cataloger/golang/config_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/anchore/go-homedir" + "github.com/anchore/syft/syft/cataloging" ) func Test_Config(t *testing.T) { @@ -58,7 +59,7 @@ func Test_Config(t *testing.T) { NoProxy: []string{"my.private", "no.proxy"}, MainModuleVersion: DefaultMainModuleVersionConfig(), UsePackagesLib: true, - CaptureSymbols: SymbolScopeNone, + CaptureSymbols: cataloging.SymbolScopeNone, }, }, { @@ -87,7 +88,7 @@ func Test_Config(t *testing.T) { NoProxy: []string{"alt.no.proxy"}, MainModuleVersion: DefaultMainModuleVersionConfig(), UsePackagesLib: true, - CaptureSymbols: SymbolScopeNone, + CaptureSymbols: cataloging.SymbolScopeNone, }, }, } @@ -114,29 +115,6 @@ func Test_Config(t *testing.T) { } } -func Test_SymbolScope_Parse(t *testing.T) { - tests := []struct { - input string - expected SymbolScope - }{ - {"all", SymbolScopeAll}, - {"ALL", SymbolScopeAll}, - {" all ", SymbolScopeAll}, - {"stdlib", SymbolScopeStdlib}, - {"Stdlib", SymbolScopeStdlib}, - {"none", SymbolScopeNone}, - {"", SymbolScopeNone}, - {"true", ""}, - {"false", ""}, - {"bogus", ""}, - } - for _, test := range tests { - t.Run(test.input, func(t *testing.T) { - assert.Equal(t, test.expected, SymbolScope(test.input).Parse()) - }) - } -} - // restoreCache ensures cache settings are restored after test func restoreCache(t testing.TB) { t.Helper() diff --git a/syft/pkg/cataloger/golang/parse_go_binary.go b/syft/pkg/cataloger/golang/parse_go_binary.go index 2cefc6058..e44732364 100644 --- a/syft/pkg/cataloger/golang/parse_go_binary.go +++ b/syft/pkg/cataloger/golang/parse_go_binary.go @@ -21,6 +21,7 @@ import ( "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/cataloging" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/internal/unionreader" "github.com/anchore/syft/syft/pkg" @@ -50,7 +51,7 @@ const devel = "(devel)" type goBinaryCataloger struct { licenseResolver goLicenseResolver mainModuleVersion MainModuleVersionConfig - symbolScope SymbolScope + symbolScope cataloging.SymbolScope // stdlibSymbols holds the standard-library function symbols discovered per binary (keyed by the // binary's location), populated during parsing and consumed by stdlibProcessor when it builds the @@ -98,7 +99,7 @@ func (c *goBinaryCataloger) parseGoBinary(ctx context.Context, resolver file.Res } defer internal.CloseAndLogError(reader.ReadCloser, reader.RealPath) - mods, errs := scanFile(reader.Location, unionReader, c.symbolScope != SymbolScopeNone) + mods, errs := scanFile(reader.Location, unionReader, c.symbolScope != cataloging.SymbolScopeNone) var rels []artifact.Relationship for _, mod := range mods { @@ -161,7 +162,7 @@ func (c *goBinaryCataloger) buildGoPkgInfo(ctx context.Context, resolver file.Re symbolsByModule, stdlibSymbols := moduleSymbols(mod.symbols, &mod.Main, mod.Deps) c.recordStdlibSymbols(location.Coordinates, stdlibSymbols) - if c.symbolScope != SymbolScopeAll { + if c.symbolScope != cataloging.SymbolScopeAll { // only the "all" scope attaches per-module symbols; for the "stdlib" scope we keep just the // recorded stdlib symbols. nil map lookups below then yield nil symbol lists for each module. symbolsByModule = nil diff --git a/syft/pkg/cataloger/golang/parse_go_binary_test.go b/syft/pkg/cataloger/golang/parse_go_binary_test.go index 14df4d4d5..648068822 100644 --- a/syft/pkg/cataloger/golang/parse_go_binary_test.go +++ b/syft/pkg/cataloger/golang/parse_go_binary_test.go @@ -18,6 +18,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/anchore/syft/syft/cataloging" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/internal/fileresolver" "github.com/anchore/syft/syft/internal/unionreader" @@ -1437,7 +1438,7 @@ func Test_buildGoPkgInfo_symbolScope(t *testing.T) { tests := []struct { name string - scope SymbolScope + scope cataloging.SymbolScope symbols []binarySymbol wantMainSyms []string wantDepSyms []string @@ -1445,18 +1446,18 @@ func Test_buildGoPkgInfo_symbolScope(t *testing.T) { }{ { name: "none captures nothing", - scope: SymbolScopeNone, + scope: cataloging.SymbolScopeNone, symbols: nil, }, { name: "stdlib captures only the stdlib package", - scope: SymbolScopeStdlib, + scope: cataloging.SymbolScopeStdlib, symbols: populatedSymbols, wantStdlibSyms: []string{"net/http.(*Client).Do"}, }, { name: "all captures module and stdlib packages", - scope: SymbolScopeAll, + scope: cataloging.SymbolScopeAll, symbols: populatedSymbols, wantMainSyms: []string{"main.main"}, wantDepSyms: []string{"github.com/foo/bar.Parse"},