diff --git a/analyzer.go b/analyzer.go index 0b1225b9b3..a985ebd22a 100644 --- a/analyzer.go +++ b/analyzer.go @@ -333,12 +333,34 @@ func (gosec *Analyzer) load(pkgPath string, conf *packages.Config) ([]*packages. return []*packages.Package{}, fmt.Errorf("importing dir %q: %w", pkgPath, err) } - var packageFiles []string - for _, filename := range basePackage.GoFiles { - packageFiles = append(packageFiles, path.Join(pkgPath, filename)) + var goModFile string + var goModDir string + if os.Getenv("DISABLE_MULTI_MODULE_MODE") != "true" { + if modPkgs, err := packages.Load(&packages.Config{Mode: packages.NeedModule, Dir: abspath}, abspath); err == nil && len(modPkgs) == 1 { + if len(modPkgs[0].Errors) != 0 { + for _, modPkgErr := range modPkgs[0].Errors { + gosec.logger.Printf("Skipping multi module mode for '%s': %s", pkgPath, modPkgErr) + } + } else { + goModFile = modPkgs[0].Module.GoMod + goModDir = path.Dir(goModFile) + } + } } - for _, filename := range basePackage.CgoFiles { - packageFiles = append(packageFiles, path.Join(pkgPath, filename)) + + var packageFiles []string + for _, filename := range append(basePackage.GoFiles, basePackage.CgoFiles...) { + if goModDir == "" { + packageFiles = append(packageFiles, path.Join(pkgPath, filename)) + } else { + filePath := path.Join(abspath, filename) + relPath, err := filepath.Rel(goModDir, filePath) + if err != nil { + gosec.logger.Printf("Skipping: %s. Cannot get relative path between %q and %q: %s", filename, goModDir, filePath, err) + continue + } + packageFiles = append(packageFiles, relPath) + } } if gosec.tests { @@ -354,6 +376,16 @@ func (gosec *Analyzer) load(pkgPath string, conf *packages.Config) ([]*packages. gosec.mu.Lock() conf.BuildFlags = nil defer gosec.mu.Unlock() + + // set conf.Dir and reset back to original value + confDir := conf.Dir + if goModDir != "" { + conf.Dir = goModDir + defer func() { + conf.Dir = confDir + }() + } + pkgs, err := packages.Load(conf, packageFiles...) if err != nil { return []*packages.Package{}, fmt.Errorf("loading files from package %q: %w", pkgPath, err) diff --git a/analyzer_test.go b/analyzer_test.go index 5d8b947afe..eb15fa95dc 100644 --- a/analyzer_test.go +++ b/analyzer_test.go @@ -2,10 +2,12 @@ package gosec_test import ( "errors" + "fmt" "log" "os" "regexp" "strings" + "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -124,6 +126,36 @@ var _ = Describe("Analyzer", func() { Expect(metrics.NumFiles).To(Equal(2)) }) + It("should be able to analyze multiple Go modules", func() { + analyzer.LoadRules(rules.Generate(false).RulesInfo()) + pkg1 := testutils.NewTestPackage() + pkg2 := testutils.NewTestPackage() + defer pkg1.Close() + defer pkg2.Close() + pkg1.AddFile("go.mod", ` + module github.com/securego/gosec/v2/test1 + `) + pkg1.AddFile("foo.go", ` + package main + func main(){ + }`) + pkg2.AddFile("go.mod", ` + module github.com/securego/gosec/v2/test2 + `) + pkg2.AddFile("bar.go", ` + package main + func bar(){ + }`) + err := pkg1.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = pkg2.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = analyzer.Process(buildTags, pkg1.Path, pkg2.Path) + Expect(err).ShouldNot(HaveOccurred()) + _, metrics, _ := analyzer.Report() + Expect(metrics.NumFiles).To(Equal(2)) + }) + It("should find errors when nosec is not in use", func() { sample := testutils.SampleCodeG401[0] source := sample.Code[0] @@ -896,3 +928,63 @@ func main() { }) }) }) + +func BenchmarkWithMultiModuleDisabled(b *testing.B) { + b.Setenv("DISABLE_MULTI_MODULE_MODE", "true") + for _, numFiles := range []int{1, 10, 100, 1000, 5000} { + b.Run(fmt.Sprintf("_%d", numFiles), func(b *testing.B) { + for i := 0; i < b.N; i++ { + benchutil(numFiles, b) + } + }) + } +} + +func BenchmarkWithMultiModuleEnabled(b *testing.B) { + for _, numFiles := range []int{1, 10, 100, 1000, 5000} { + b.Run(fmt.Sprintf("_%d", numFiles), func(b *testing.B) { + for i := 0; i < b.N; i++ { + benchutil(numFiles, b) + } + }) + } +} + +func benchutil(numFiles int, t testing.TB) { + logger, _ := testutils.NewLogger() + analyzer := gosec.NewAnalyzer(nil, false, false, false, 1, logger) + analyzer.LoadRules(rules.Generate(false).RulesInfo()) + pkg1 := testutils.NewTestPackage() + defer pkg1.Close() + pkg1.AddFile("go.mod", ` + module github.com/securego/gosec/v2/test2 + `) + + pkg1.AddFile("foo.go", ` + package main + func main(){} + `) + + for i := 0; i < numFiles; i++ { + filename := fmt.Sprintf("foo_%d.go", i) + pkg1.AddFile(filename, fmt.Sprintf(` + package main + func SomeFunc%d(){} + `, i)) + } + + err := pkg1.Build() + if err != nil { + t.Fatal(err) + } + + err = analyzer.Process([]string{}, pkg1.Path) + if err != nil { + t.Fatal(err) + } + + _, _, errs := analyzer.Report() + if errs != nil && len(errs) != 0 { + t.Fatal(errs) + } +}