From 539b6ab709bad5f21985b607fdd661613b7a9e69 Mon Sep 17 00:00:00 2001 From: knqyf263 Date: Fri, 22 Nov 2024 14:08:50 +0400 Subject: [PATCH] feat: handle orphan indirect depenencies Signed-off-by: knqyf263 --- pkg/fanal/analyzer/language/golang/mod/mod.go | 37 +++++++++++ .../analyzer/language/golang/mod/mod_test.go | 63 +++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/pkg/fanal/analyzer/language/golang/mod/mod.go b/pkg/fanal/analyzer/language/golang/mod/mod.go index 52d7b32f3bee..bb6117f3a100 100644 --- a/pkg/fanal/analyzer/language/golang/mod/mod.go +++ b/pkg/fanal/analyzer/language/golang/mod/mod.go @@ -101,6 +101,9 @@ func (a *gomodAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalys a.logger.Warn("Unable to collect additional info", log.Err(err)) } + // Add orphan indirect dependencies under the main module + a.addOrphanIndirectDepsUnderRoot(apps) + return &analyzer.AnalysisResult{ Applications: apps, }, nil @@ -212,6 +215,40 @@ func (a *gomodAnalyzer) collectDeps(modDir, pkgID string) (types.Dependency, err }, nil } +// addOrphanIndirectDepsUnderRoot handles indirect dependencies that have no identifiable parent packages in the dependency tree. +// This situation can occur when: +// - $GOPATH/pkg directory doesn't exist +// - Module cache is incomplete +// - etc. +// +// In such cases, indirect packages become "orphaned" - they exist in the dependency list +// but have no connection to the dependency tree. This function resolves this issue by: +// 1. Finding the root (main) module +// 2. Identifying all indirect dependencies that have no parent packages +// 3. Adding these orphaned indirect dependencies under the main module +// +// This ensures that all packages remain visible in the dependency tree, even when the complete +// dependency chain cannot be determined. +func (a *gomodAnalyzer) addOrphanIndirectDepsUnderRoot(apps []types.Application) { + for _, app := range apps { + // Find the main module + _, rootIdx, found := lo.FindIndexOf(app.Packages, func(pkg types.Package) bool { + return pkg.Relationship == types.RelationshipRoot + }) + if !found { + continue + } + + // Collect all orphan indirect dependencies that are unable to identify parents + parents := app.Packages.ParentDeps() + orphanDeps := lo.FilterMap(app.Packages, func(pkg types.Package, _ int) (string, bool) { + return pkg.ID, pkg.Relationship == types.RelationshipIndirect && len(parents[pkg.ID]) == 0 + }) + // Add orphan indirect dependencies under the main module + app.Packages[rootIdx].DependsOn = append(app.Packages[rootIdx].DependsOn, orphanDeps...) + } +} + func parse(fsys fs.FS, path string, parser language.Parser) (*types.Application, error) { f, err := fsys.Open(path) if err != nil { diff --git a/pkg/fanal/analyzer/language/golang/mod/mod_test.go b/pkg/fanal/analyzer/language/golang/mod/mod_test.go index d0608c3a1130..c2e0370d172a 100644 --- a/pkg/fanal/analyzer/language/golang/mod/mod_test.go +++ b/pkg/fanal/analyzer/language/golang/mod/mod_test.go @@ -116,6 +116,69 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) { }, }, }, + { + name: "no pkg dir found", + files: []string{ + "testdata/no-pkg-found/mod", + }, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.GoModule, + FilePath: "go.mod", + Packages: types.Packages{ + { + ID: "github.com/org/repo", + Name: "github.com/org/repo", + Relationship: types.RelationshipRoot, + DependsOn: []string{ + "github.com/aquasecurity/go-dep-parser@v1.0.0", + "github.com/aquasecurity/go-version@v1.0.1", + "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", // No parent found, so it's added here. + }, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/org/repo", + }, + }, + }, + { + ID: "github.com/aquasecurity/go-dep-parser@v1.0.0", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "v1.0.0", + Relationship: types.RelationshipDirect, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/aquasecurity/go-dep-parser", + }, + }, + }, + { + ID: "github.com/aquasecurity/go-version@v1.0.1", + Name: "github.com/aquasecurity/go-version", + Version: "v1.0.1", + Relationship: types.RelationshipDirect, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/aquasecurity/go-version", + }, + }, + }, + { + ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", + Name: "golang.org/x/xerrors", + Version: "v0.0.0-20200804184101-5ec99f83aff1", + Relationship: types.RelationshipIndirect, + Indirect: true, + }, + }, + }, + }, + }, + }, { name: "less than 1.17", files: []string{