diff --git a/syft/pkg/cataloger/golang/parse_go_mod.go b/syft/pkg/cataloger/golang/parse_go_mod.go index d593ed299..4a40403b1 100644 --- a/syft/pkg/cataloger/golang/parse_go_mod.go +++ b/syft/pkg/cataloger/golang/parse_go_mod.go @@ -72,7 +72,7 @@ func (c *goModCataloger) parseGoModFile(ctx context.Context, resolver file.Resol // only use go.mod packages NOT found in source analysis goModPackages := c.createGoModPackages(ctx, resolver, modFile, sourceModules, reader, digests) - c.applyReplaceDirectives(ctx, resolver, modFile, goModPackages, reader, digests) + c.applyReplaceDirectives(ctx, resolver, modFile, sourceModules, goModPackages, reader, digests) c.applyExcludeDirectives(modFile, goModPackages) pkgs = c.assembleResults(catalogedModules, goModPackages) @@ -365,8 +365,11 @@ func (c *goModCataloger) createGoModPackages(ctx context.Context, resolver file. } // applyReplaceDirectives processes replace directives from go.mod -func (c *goModCataloger) applyReplaceDirectives(ctx context.Context, resolver file.Resolver, modFile *modfile.File, goModPackages map[string]pkg.Package, reader file.LocationReadCloser, digests map[string]string) { +func (c *goModCataloger) applyReplaceDirectives(ctx context.Context, resolver file.Resolver, modFile *modfile.File, sourceModules map[string]*packages.Module, goModPackages map[string]pkg.Package, reader file.LocationReadCloser, digests map[string]string) { for _, m := range modFile.Replace { + if sourceModules != nil && sourceModules[m.Old.Path] != nil { + continue + } lics := c.licenseResolver.getLicenses(ctx, resolver, m.New.Path, m.New.Version) var finalPath string if !strings.HasPrefix(m.New.Path, ".") && !strings.HasPrefix(m.New.Path, "/") { diff --git a/syft/pkg/cataloger/golang/parse_go_mod_replace_test.go b/syft/pkg/cataloger/golang/parse_go_mod_replace_test.go new file mode 100644 index 000000000..8e51fd2a8 --- /dev/null +++ b/syft/pkg/cataloger/golang/parse_go_mod_replace_test.go @@ -0,0 +1,44 @@ +package golang + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + stereofile "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" + "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/source/directorysource" +) + +func Test_parseGoSource_replacedModulesAreNotDuplicated(t *testing.T) { + fixture := filepath.Join("testdata", "go-source-replacements") + s, err := directorysource.NewFromPath(fixture) + require.NoError(t, err) + resolver, err := s.FileResolver(source.AllLayersScope) + require.NoError(t, err) + + modPath, err := filepath.Abs(filepath.Join(fixture, "go.mod")) + require.NoError(t, err) + contents, err := os.Open(modPath) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, contents.Close()) }) + + reader := file.LocationReadCloser{ + Location: file.NewVirtualLocationFromDirectory("go.mod", "go.mod", *stereofile.NewFileReference(stereofile.Path(modPath))), + ReadCloser: contents, + } + config := DefaultCatalogerConfig().WithUsePackagesLib(true).WithSearchRemoteLicenses(false) + pkgs, _, err := newGoModCataloger(config).parseGoModFile(pkgtest.Context(t), resolver, nil, reader) + require.NoError(t, err) + + versions := make(map[string][]string) + for _, p := range pkgs { + versions[p.Name] = append(versions[p.Name], p.Version) + } + require.Equal(t, []string{"v1.5.2"}, versions["rsc.io/quote"]) + require.Equal(t, []string{"v1.3.1"}, versions["rsc.io/sampler"]) +} diff --git a/syft/pkg/cataloger/golang/testdata/go-source-replacements/go.mod b/syft/pkg/cataloger/golang/testdata/go-source-replacements/go.mod new file mode 100644 index 000000000..b9066bb7c --- /dev/null +++ b/syft/pkg/cataloger/golang/testdata/go-source-replacements/go.mod @@ -0,0 +1,15 @@ +module example.com/repro + +go 1.23 + +require rsc.io/quote v1.5.1 + +require ( + golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect + rsc.io/sampler v1.3.0 // indirect +) + +replace ( + rsc.io/quote => rsc.io/quote v1.5.2 + rsc.io/sampler => rsc.io/sampler v1.3.1 +) diff --git a/syft/pkg/cataloger/golang/testdata/go-source-replacements/go.sum b/syft/pkg/cataloger/golang/testdata/go-source-replacements/go.sum new file mode 100644 index 000000000..715747812 --- /dev/null +++ b/syft/pkg/cataloger/golang/testdata/go-source-replacements/go.sum @@ -0,0 +1,6 @@ +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y= +rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0= +rsc.io/sampler v1.3.1 h1:F0c3J2nQCdk9ODsNhU3sElnvPIxM/xV1c/qZuAeZmac= +rsc.io/sampler v1.3.1/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/syft/pkg/cataloger/golang/testdata/go-source-replacements/main.go b/syft/pkg/cataloger/golang/testdata/go-source-replacements/main.go new file mode 100644 index 000000000..0a29866c4 --- /dev/null +++ b/syft/pkg/cataloger/golang/testdata/go-source-replacements/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" + + "rsc.io/quote" +) + +func main() { + fmt.Println(quote.Hello()) +}