From 778df828eaad9827cb833c6285058a33aa2b83ca Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Thu, 17 Oct 2024 12:54:12 +0600 Subject: [PATCH] fix(java): correctly inherit `version` and `scope` from upper/root `depManagement` and `dependencies` into parents (#7541) Signed-off-by: knqyf263 Co-authored-by: knqyf263 --- pkg/dependency/parser/java/pom/parse.go | 147 ++++---- pkg/dependency/parser/java/pom/parse_test.go | 344 ++++++++++++++++++ pkg/dependency/parser/java/pom/pom.go | 29 +- .../pom.xml | 29 ++ .../pom.xml | 64 ++++ .../parent/pom.xml | 77 ++++ .../pom.xml | 39 ++ .../example-nested-scope-compile-1.0.0.pom | 41 +++ .../1.0.0/parent/pom.xml | 21 ++ .../example-nested-scope-empty-1.0.0.pom | 40 ++ .../1.0.0/parent/pom.xml | 21 ++ .../example-nested-scope-runtime-1.0.0.pom | 41 +++ .../1.0.0/parent/pom.xml | 21 ++ .../2.0.0/example-scope-compile-2.0.0.pom | 23 ++ .../2.0.0/example-scope-empty-2.0.0.pom | 22 ++ .../2.0.0/example-scope-runtime-2.0.0.pom | 23 ++ .../testdata/transitive-parents/base/pom.xml | 2 +- .../parent/pom.xml | 34 ++ .../pom.xml | 45 +++ .../top-parent/pom.xml | 34 ++ 20 files changed, 1013 insertions(+), 84 deletions(-) create mode 100644 pkg/dependency/parser/java/pom/testdata/inherit-scopes-from-child-deps-and-their-parents/pom.xml create mode 100644 pkg/dependency/parser/java/pom/testdata/inherit-scopes-in-children-from-root/pom.xml create mode 100644 pkg/dependency/parser/java/pom/testdata/inherit-scopes-in-parents-from-root/parent/pom.xml create mode 100644 pkg/dependency/parser/java/pom/testdata/inherit-scopes-in-parents-from-root/pom.xml create mode 100644 pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-compile/1.0.0/example-nested-scope-compile-1.0.0.pom create mode 100644 pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-compile/1.0.0/parent/pom.xml create mode 100644 pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-empty/1.0.0/example-nested-scope-empty-1.0.0.pom create mode 100644 pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-empty/1.0.0/parent/pom.xml create mode 100644 pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-runtime/1.0.0/example-nested-scope-runtime-1.0.0.pom create mode 100644 pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-runtime/1.0.0/parent/pom.xml create mode 100644 pkg/dependency/parser/java/pom/testdata/repository/org/example/example-scope-compile/2.0.0/example-scope-compile-2.0.0.pom create mode 100644 pkg/dependency/parser/java/pom/testdata/repository/org/example/example-scope-empty/2.0.0/example-scope-empty-2.0.0.pom create mode 100644 pkg/dependency/parser/java/pom/testdata/repository/org/example/example-scope-runtime/2.0.0/example-scope-runtime-2.0.0.pom create mode 100644 pkg/dependency/parser/java/pom/testdata/use-child-dep-management-in-parent/parent/pom.xml create mode 100644 pkg/dependency/parser/java/pom/testdata/use-child-dep-management-in-parent/pom.xml create mode 100644 pkg/dependency/parser/java/pom/testdata/use-child-dep-management-in-parent/top-parent/pom.xml diff --git a/pkg/dependency/parser/java/pom/parse.go b/pkg/dependency/parser/java/pom/parse.go index 542abcac3c8a..2c8a194ea50b 100644 --- a/pkg/dependency/parser/java/pom/parse.go +++ b/pkg/dependency/parser/java/pom/parse.go @@ -96,7 +96,7 @@ func NewParser(filePath string, opts ...option) *Parser { } func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { - content, err := parsePom(r) + content, err := parsePom(r, true) if err != nil { return nil, nil, xerrors.Errorf("failed to parse POM: %w", err) } @@ -107,7 +107,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc } // Analyze root POM - result, err := p.analyze(root, analysisOptions{lineNumber: true}) + result, err := p.analyze(root, analysisOptions{}) if err != nil { return nil, nil, xerrors.Errorf("analyze error (%s): %w", p.rootPath, err) } @@ -330,53 +330,27 @@ type analysisResult struct { type analysisOptions struct { exclusions map[string]struct{} depManagement []pomDependency // from the root POM - lineNumber bool // Save line numbers } func (p *Parser) analyze(pom *pom, opts analysisOptions) (analysisResult, error) { - if pom == nil || pom.content == nil { + if pom.nil() { return analysisResult{}, nil } - // Update remoteRepositories pomReleaseRemoteRepos, pomSnapshotRemoteRepos := pom.repositories(p.servers) p.releaseRemoteRepos = lo.Uniq(append(pomReleaseRemoteRepos, p.releaseRemoteRepos...)) p.snapshotRemoteRepos = lo.Uniq(append(pomSnapshotRemoteRepos, p.snapshotRemoteRepos...)) - // We need to forward dependencyManagements from current and root pom to Parent, - // to use them for dependencies in parent. - // For better understanding see the following tests: - // - `dependency from parent uses version from child pom depManagement` - // - `dependency from parent uses version from root pom depManagement` - // - // depManagements from root pom has higher priority than depManagements from current pom. - depManagementForParent := lo.UniqBy(append(opts.depManagement, pom.content.DependencyManagement.Dependencies.Dependency...), - func(dep pomDependency) string { - return dep.Name() - }) - - // Parent - parent, err := p.parseParent(pom.filePath, pom.content.Parent, depManagementForParent) - if err != nil { - return analysisResult{}, xerrors.Errorf("parent error: %w", err) + // Resolve parent POM + if err := p.resolveParent(pom); err != nil { + return analysisResult{}, xerrors.Errorf("pom resolve error: %w", err) } - // Inherit values/properties from parent - pom.inherit(parent) - - // Generate properties + // Resolve dependencies props := pom.properties() - - // dependencyManagements have the next priority: - // 1. Managed dependencies from this POM - // 2. Managed dependencies from parent of this POM - depManagement := p.mergeDependencyManagements(pom.content.DependencyManagement.Dependencies.Dependency, - parent.dependencyManagement) - - // Merge dependencies. Child dependencies must be preferred than parent dependencies. - // Parents don't have to resolve dependencies. + depManagement := pom.content.DependencyManagement.Dependencies.Dependency deps := p.parseDependencies(pom.content.Dependencies.Dependency, props, depManagement, opts) - deps = p.mergeDependencies(parent.dependencies, deps, opts.exclusions) + deps = p.filterDependencies(deps, opts.exclusions) return analysisResult{ filePath: pom.filePath, @@ -388,6 +362,39 @@ func (p *Parser) analyze(pom *pom, opts analysisOptions) (analysisResult, error) }, nil } +// resolveParent resolves its parent POMs and inherits properties, dependencies, and dependencyManagement. +func (p *Parser) resolveParent(pom *pom) error { + if pom.nil() { + return nil + } + + // Parse parent POM + parent, err := p.parseParent(pom.filePath, pom.content.Parent) + if err != nil { + return xerrors.Errorf("parent error: %w", err) + } + + // Inherit values/properties from parent + pom.inherit(parent) + + // Merge properties + pom.content.Properties = p.mergeProperties(pom.content.Properties, parent.content.Properties) + + // Merge dependencyManagement with the following priority: + // 1. Managed dependencies from this POM + // 2. Managed dependencies from parent of this POM + pom.content.DependencyManagement.Dependencies.Dependency = p.mergeDependencyManagements( + pom.content.DependencyManagement.Dependencies.Dependency, + parent.content.DependencyManagement.Dependencies.Dependency) + + // Merge dependencies + pom.content.Dependencies.Dependency = p.mergeDependencies( + pom.content.Dependencies.Dependency, + parent.content.Dependencies.Dependency) + + return nil +} + func (p *Parser) mergeDependencyManagements(depManagements ...[]pomDependency) []pomDependency { uniq := make(map[string]struct{}) var depManagement []pomDependency @@ -463,22 +470,20 @@ func (p *Parser) resolveDepManagement(props map[string]string, depManagement []p return newDepManagement } -func (p *Parser) mergeDependencies(parent, child []artifact, exclusions map[string]struct{}) []artifact { - var deps []artifact - unique := make(map[string]struct{}) +func (p *Parser) mergeProperties(child, parent properties) properties { + return lo.Assign(parent, child) +} - for _, d := range append(child, parent...) { - if excludeDep(exclusions, d) { - continue - } - if _, ok := unique[d.Name()]; ok { - continue - } - unique[d.Name()] = struct{}{} - deps = append(deps, d) - } +func (p *Parser) mergeDependencies(child, parent []pomDependency) []pomDependency { + return lo.UniqBy(append(child, parent...), func(d pomDependency) string { + return d.Name() + }) +} - return deps +func (p *Parser) filterDependencies(artifacts []artifact, exclusions map[string]struct{}) []artifact { + return lo.Filter(artifacts, func(art artifact, _ int) bool { + return !excludeDep(exclusions, art) + }) } func excludeDep(exclusions map[string]struct{}, art artifact) bool { @@ -497,38 +502,29 @@ func excludeDep(exclusions map[string]struct{}, art artifact) bool { return false } -func (p *Parser) parseParent(currentPath string, parent pomParent, rootDepManagement []pomDependency) (analysisResult, error) { +func (p *Parser) parseParent(currentPath string, parent pomParent) (*pom, error) { // Pass nil properties so that variables in are not evaluated. target := newArtifact(parent.GroupId, parent.ArtifactId, parent.Version, nil, nil) // if version is property (e.g. ${revision}) - we still need to parse this pom if target.IsEmpty() && !isProperty(parent.Version) { - return analysisResult{}, nil + return &pom{content: &pomXML{}}, nil } logger := p.logger.With("artifact", target.String()) logger.Debug("Start parent") defer logger.Debug("Exit parent") - // If the artifact is found in cache, it is returned. - if result := p.cache.get(target); result != nil { - return *result, nil - } - parentPOM, err := p.retrieveParent(currentPath, parent.RelativePath, target) if err != nil { logger.Debug("Parent POM not found", log.Err(err)) + return &pom{content: &pomXML{}}, nil } - result, err := p.analyze(parentPOM, analysisOptions{ - depManagement: rootDepManagement, - }) - if err != nil { - return analysisResult{}, xerrors.Errorf("analyze error: %w", err) + if err = p.resolveParent(parentPOM); err != nil { + return nil, xerrors.Errorf("parent pom resolve error: %w", err) } - p.cache.put(target, result) - - return result, nil + return parentPOM, nil } func (p *Parser) retrieveParent(currentPath, relativePath string, target artifact) (*pom, error) { @@ -565,7 +561,7 @@ func (p *Parser) retrieveParent(currentPath, relativePath string, target artifac } func (p *Parser) tryRelativePath(parentArtifact artifact, currentPath, relativePath string) (*pom, error) { - pom, err := p.openRelativePom(currentPath, relativePath) + parsedPOM, err := p.openRelativePom(currentPath, relativePath) if err != nil { return nil, err } @@ -576,19 +572,18 @@ func (p *Parser) tryRelativePath(parentArtifact artifact, currentPath, relativeP // But GroupID can be inherited from parent (`p.analyze` function is required to get the GroupID). // Version can contain a property (`p.analyze` function is required to get the GroupID). // So we can only match ArtifactID's. - if pom.artifact().ArtifactID != parentArtifact.ArtifactID { + if parsedPOM.artifact().ArtifactID != parentArtifact.ArtifactID { return nil, xerrors.New("'parent.relativePath' points at wrong local POM") } - result, err := p.analyze(pom, analysisOptions{}) - if err != nil { + if err := p.resolveParent(parsedPOM); err != nil { return nil, xerrors.Errorf("analyze error: %w", err) } - if !parentArtifact.Equal(result.artifact) { + if !parentArtifact.Equal(parsedPOM.artifact()) { return nil, xerrors.New("'parent.relativePath' points at wrong local POM") } - return pom, nil + return parsedPOM, nil } func (p *Parser) openRelativePom(currentPath, relativePath string) (*pom, error) { @@ -620,7 +615,7 @@ func (p *Parser) openPom(filePath string) (*pom, error) { } defer f.Close() - content, err := parsePom(f) + content, err := parsePom(f, false) if err != nil { return nil, xerrors.Errorf("failed to parse the local POM: %w", err) } @@ -777,7 +772,7 @@ func (p *Parser) fetchPOMFromRemoteRepository(repo string, paths []string) (*pom } defer resp.Body.Close() - content, err := parsePom(resp.Body) + content, err := parsePom(resp.Body, false) if err != nil { return nil, xerrors.Errorf("failed to parse the remote POM: %w", err) } @@ -788,13 +783,19 @@ func (p *Parser) fetchPOMFromRemoteRepository(repo string, paths []string) (*pom }, nil } -func parsePom(r io.Reader) (*pomXML, error) { +func parsePom(r io.Reader, lineNumber bool) (*pomXML, error) { parsed := &pomXML{} decoder := xml.NewDecoder(r) decoder.CharsetReader = charset.NewReaderLabel if err := decoder.Decode(parsed); err != nil { return nil, xerrors.Errorf("xml decode error: %w", err) } + if !lineNumber { + for i := range parsed.Dependencies.Dependency { + parsed.Dependencies.Dependency[i].StartLine = 0 + parsed.Dependencies.Dependency[i].EndLine = 0 + } + } return parsed, nil } diff --git a/pkg/dependency/parser/java/pom/parse_test.go b/pkg/dependency/parser/java/pom/parse_test.go index 82604846a4f3..7b77c0a7ef5e 100644 --- a/pkg/dependency/parser/java/pom/parse_test.go +++ b/pkg/dependency/parser/java/pom/parse_test.go @@ -14,6 +14,100 @@ import ( ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) +var ( + exampleNestedScopeCompile = func(start, end int) ftypes.Package { + var location ftypes.Locations + if start != 0 && end != 0 { + location = append(location, ftypes.Location{ + StartLine: start, + EndLine: end, + }) + } + return ftypes.Package{ + ID: "org.example:example-nested-scope-compile:1.0.0", + Name: "org.example:example-nested-scope-compile", + Version: "1.0.0", + Relationship: ftypes.RelationshipDirect, + Locations: location, + } + } + + exampleNestedScopeEmpty = func(start, end int) ftypes.Package { + var location ftypes.Locations + if start != 0 && end != 0 { + location = append(location, ftypes.Location{ + StartLine: start, + EndLine: end, + }) + } + return ftypes.Package{ + ID: "org.example:example-nested-scope-empty:1.0.0", + Name: "org.example:example-nested-scope-empty", + Version: "1.0.0", + Relationship: ftypes.RelationshipDirect, + Locations: location, + } + } + + exampleNestedScopeRuntime = func(start, end int) ftypes.Package { + var location ftypes.Locations + if start != 0 && end != 0 { + location = append(location, ftypes.Location{ + StartLine: start, + EndLine: end, + }) + } + return ftypes.Package{ + ID: "org.example:example-nested-scope-runtime:1.0.0", + Name: "org.example:example-nested-scope-runtime", + Version: "1.0.0", + Relationship: ftypes.RelationshipDirect, + Locations: location, + } + } + + exampleScopeCompile = ftypes.Package{ + ID: "org.example:example-scope-compile:2.0.0", + Name: "org.example:example-scope-compile", + Version: "2.0.0", + Relationship: ftypes.RelationshipIndirect, + } + + exampleScopeEmpty = ftypes.Package{ + ID: "org.example:example-scope-empty:2.0.0", + Name: "org.example:example-scope-empty", + Version: "2.0.0", + Relationship: ftypes.RelationshipIndirect, + } + + exampleScopeRuntime = ftypes.Package{ + ID: "org.example:example-scope-runtime:2.0.0", + Name: "org.example:example-scope-runtime", + Version: "2.0.0", + Relationship: ftypes.RelationshipIndirect, + } + exampleApiCompile = ftypes.Package{ + ID: "org.example:example-api-compile:3.0.0", + Name: "org.example:example-api-compile", + Version: "3.0.0", + Relationship: ftypes.RelationshipIndirect, + } + + exampleApiEmpty = ftypes.Package{ + ID: "org.example:example-api-empty:3.0.0", + Name: "org.example:example-api-empty", + Version: "3.0.0", + Relationship: ftypes.RelationshipIndirect, + } + + exampleApiRuntime = ftypes.Package{ + ID: "org.example:example-api-runtime:3.0.0", + Name: "org.example:example-api-runtime", + Version: "3.0.0", + Relationship: ftypes.RelationshipIndirect, + } +) + func TestPom_Parse(t *testing.T) { tests := []struct { name string @@ -1630,6 +1724,256 @@ func TestPom_Parse(t *testing.T) { }, }, }, + // [INFO] com.example:child-depManagement-in-parent:jar:1.0.0 + // [INFO] +- org.example:example-api2:jar:1.0.2:runtime + // [INFO] +- org.example:example-api3:jar:4.0.3:compile + // [INFO] \- org.example:example-api:jar:1.0.1:compile + { + name: "dependency from parent uses version from child(scanned) pom depManagement", + inputFile: filepath.Join("testdata", "use-child-dep-management-in-parent", "pom.xml"), + local: true, + want: []ftypes.Package{ + { + ID: "com.example:child-depManagement-in-parent:1.0.0", + Name: "com.example:child-depManagement-in-parent", + Version: "1.0.0", + Relationship: ftypes.RelationshipRoot, + }, + { + ID: "org.example:example-api:1.0.1", + Name: "org.example:example-api", + Version: "1.0.1", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "org.example:example-api2:1.0.2", + Name: "org.example:example-api2", + Version: "1.0.2", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "org.example:example-api3:4.0.3", + Name: "org.example:example-api3", + Version: "4.0.3", + Relationship: ftypes.RelationshipDirect, + }, + }, + wantDeps: []ftypes.Dependency{ + { + ID: "com.example:child-depManagement-in-parent:1.0.0", + DependsOn: []string{ + "org.example:example-api2:1.0.2", + "org.example:example-api3:4.0.3", + "org.example:example-api:1.0.1", + }, + }, + }, + }, + // [INFO] com.example:inherit-scopes-from-child-deps-and-their-parents:jar:0.0.1 + // [INFO] +- org.example:example-nested-scope-runtime:jar:1.0.0:runtime + // [INFO] | \- org.example:example-scope-runtime:jar:2.0.0:runtime + // [INFO] | \- org.example:example-api-runtime:jar:3.0.0:runtime + // [INFO] +- org.example:example-nested-scope-compile:jar:1.0.0:compile + // [INFO] | \- org.example:example-scope-compile:jar:2.0.0:compile + // [INFO] | \- org.example:example-api-compile:jar:3.0.0:compile + // [INFO] \- org.example:example-nested-scope-empty:jar:1.0.0:compile + // [INFO] \- org.example:example-scope-empty:jar:2.0.0:compile + // [INFO] \- org.example:example-api-empty:jar:3.0.0:compile + // + // `example-nested-*" dependencies and their parents contain `dependencyManagement` with changed scopes + { + name: "inherit scopes from child dependencies and their parents", + inputFile: filepath.Join("testdata", "inherit-scopes-from-child-deps-and-their-parents", "pom.xml"), + local: true, + want: []ftypes.Package{ + { + ID: "com.example:inherit-scopes-from-child-deps-and-their-parents:0.0.1", + Name: "com.example:inherit-scopes-from-child-deps-and-their-parents", + Version: "0.0.1", + Relationship: ftypes.RelationshipRoot, + }, + exampleNestedScopeCompile(16, 21), + exampleNestedScopeEmpty(22, 26), + exampleNestedScopeRuntime(10, 15), + exampleApiCompile, + exampleApiEmpty, + exampleApiRuntime, + exampleScopeCompile, + exampleScopeEmpty, + exampleScopeRuntime, + }, + wantDeps: []ftypes.Dependency{ + { + ID: "com.example:inherit-scopes-from-child-deps-and-their-parents:0.0.1", + DependsOn: []string{ + "org.example:example-nested-scope-compile:1.0.0", + "org.example:example-nested-scope-empty:1.0.0", + "org.example:example-nested-scope-runtime:1.0.0", + }, + }, + { + ID: "org.example:example-nested-scope-compile:1.0.0", + DependsOn: []string{ + "org.example:example-scope-compile:2.0.0", + }, + }, + { + ID: "org.example:example-nested-scope-empty:1.0.0", + DependsOn: []string{ + "org.example:example-scope-empty:2.0.0", + }, + }, + { + ID: "org.example:example-nested-scope-runtime:1.0.0", + DependsOn: []string{ + "org.example:example-scope-runtime:2.0.0", + }, + }, + { + ID: "org.example:example-scope-compile:2.0.0", + DependsOn: []string{ + "org.example:example-api-compile:3.0.0", + }, + }, + { + ID: "org.example:example-scope-empty:2.0.0", + DependsOn: []string{ + "org.example:example-api-empty:3.0.0", + }, + }, + { + ID: "org.example:example-scope-runtime:2.0.0", + DependsOn: []string{ + "org.example:example-api-runtime:3.0.0", + }, + }, + }, + }, + // [INFO] com.example:inherit-scopes-in-parents-from-root:jar:0.1.0 + // [INFO] +- org.example:example-nested-scope-runtime:jar:1.0.0:runtime + // [INFO] | \- org.example:example-scope-runtime:jar:2.0.0:compile + // [INFO] | \- org.example:example-api-runtime:jar:3.0.0:runtime + // [INFO] +- org.example:example-nested-scope-compile:jar:1.0.0:compile + // [INFO] | \- org.example:example-scope-compile:jar:2.0.0:runtime + // [INFO] | \- org.example:example-api-compile:jar:3.0.0:test + // [INFO] \- org.example:example-nested-scope-empty:jar:1.0.0:compile + // [INFO] \- org.example:example-scope-empty:jar:2.0.0:runtime + // [INFO] \- org.example:example-api-empty:jar:3.0.0:test + // + // `example-nested-*" dependencies and their parents contain `dependencyManagement` with changed scopes + // scopes from `dependencyManagement` of root pom are used + { + name: "inherit scopes in children from root pom", + inputFile: filepath.Join("testdata", "inherit-scopes-in-children-from-root", "pom.xml"), + local: true, + want: []ftypes.Package{ + { + ID: "com.example:inherit-scopes-in-children-from-root:0.0.1", + Name: "com.example:inherit-scopes-in-children-from-root", + Version: "0.0.1", + Relationship: ftypes.RelationshipRoot, + }, + exampleNestedScopeCompile(51, 56), + exampleNestedScopeEmpty(57, 61), + exampleNestedScopeRuntime(45, 50), + exampleApiRuntime, + exampleScopeCompile, + exampleScopeEmpty, + exampleScopeRuntime, + }, + wantDeps: []ftypes.Dependency{ + { + ID: "com.example:inherit-scopes-in-children-from-root:0.0.1", + DependsOn: []string{ + "org.example:example-nested-scope-compile:1.0.0", + "org.example:example-nested-scope-empty:1.0.0", + "org.example:example-nested-scope-runtime:1.0.0", + }, + }, + { + ID: "org.example:example-nested-scope-compile:1.0.0", + DependsOn: []string{ + "org.example:example-scope-compile:2.0.0", + }, + }, + { + ID: "org.example:example-nested-scope-empty:1.0.0", + DependsOn: []string{ + "org.example:example-scope-empty:2.0.0", + }, + }, + { + ID: "org.example:example-nested-scope-runtime:1.0.0", + DependsOn: []string{ + "org.example:example-scope-runtime:2.0.0", + }, + }, + { + ID: "org.example:example-scope-runtime:2.0.0", + DependsOn: []string{ + "org.example:example-api-runtime:3.0.0", + }, + }, + }, + }, + // [INFO] com.example:inherit-scopes-in-parents-from-root:jar:0.1.0 + // [INFO] +- org.example:example-nested-scope-runtime:jar:1.0.0:runtime + // [INFO] | \- org.example:example-scope-runtime:jar:2.0.0:compile + // [INFO] | \- org.example:example-api-runtime:jar:3.0.0:runtime + // [INFO] +- org.example:example-nested-scope-compile:jar:1.0.0:compile + // [INFO] | \- org.example:example-scope-compile:jar:2.0.0:runtime + // [INFO] | \- org.example:example-api-compile:jar:3.0.0:test + // [INFO] \- org.example:example-nested-scope-empty:jar:1.0.0:test + // [INFO] \- org.example:example-scope-empty:jar:2.0.0:test + // [INFO] \- org.example:example-api-empty:jar:3.0.0:test + // + // `example-nested-*" dependencies and their parents contain `dependencyManagement` with changed scopes + // scopes from `dependencyManagement` of root pom are used in parent dependencies + { + name: "inherit scopes in parent from root pom", + inputFile: filepath.Join("testdata", "inherit-scopes-in-parents-from-root", "pom.xml"), + local: true, + want: []ftypes.Package{ + { + ID: "com.example:inherit-scopes-in-parents-from-root:0.1.0", + Name: "com.example:inherit-scopes-in-parents-from-root", + Version: "0.1.0", + Relationship: ftypes.RelationshipRoot, + }, + exampleNestedScopeCompile(0, 0), + exampleNestedScopeRuntime(0, 0), + exampleApiRuntime, + exampleScopeCompile, + exampleScopeRuntime, + }, + wantDeps: []ftypes.Dependency{ + { + ID: "com.example:inherit-scopes-in-parents-from-root:0.1.0", + DependsOn: []string{ + "org.example:example-nested-scope-compile:1.0.0", + "org.example:example-nested-scope-runtime:1.0.0", + }, + }, + { + ID: "org.example:example-nested-scope-compile:1.0.0", + DependsOn: []string{ + "org.example:example-scope-compile:2.0.0", + }, + }, + { + ID: "org.example:example-nested-scope-runtime:1.0.0", + DependsOn: []string{ + "org.example:example-scope-runtime:2.0.0", + }, + }, + { + ID: "org.example:example-scope-runtime:2.0.0", + DependsOn: []string{ + "org.example:example-api-runtime:3.0.0", + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/dependency/parser/java/pom/pom.go b/pkg/dependency/parser/java/pom/pom.go index 889d107c3c6c..83c5d4fec609 100644 --- a/pkg/dependency/parser/java/pom/pom.go +++ b/pkg/dependency/parser/java/pom/pom.go @@ -23,11 +23,18 @@ type pom struct { content *pomXML } -func (p *pom) inherit(result analysisResult) { +func (p *pom) nil() bool { + return p == nil || p.content == nil +} + +func (p *pom) inherit(parent *pom) { + if parent == nil { + return + } // Merge properties - p.content.Properties = utils.MergeMaps(result.properties, p.content.Properties) + p.content.Properties = utils.MergeMaps(parent.properties(), p.content.Properties) - art := p.artifact().Inherit(result.artifact) + art := p.artifact().Inherit(parent.artifact()) p.content.GroupId = art.GroupID p.content.ArtifactId = art.ArtifactID @@ -40,12 +47,12 @@ func (p *pom) inherit(result analysisResult) { } } -func (p pom) properties() properties { +func (p *pom) properties() properties { props := p.content.Properties return utils.MergeMaps(props, p.projectProperties()) } -func (p pom) projectProperties() map[string]string { +func (p *pom) projectProperties() map[string]string { val := reflect.ValueOf(p.content).Elem() props := p.listProperties(val) @@ -73,7 +80,7 @@ func (p pom) projectProperties() map[string]string { return projectProperties } -func (p pom) listProperties(val reflect.Value) map[string]string { +func (p *pom) listProperties(val reflect.Value) map[string]string { props := make(map[string]string) for i := 0; i < val.NumField(); i++ { f := val.Type().Field(i) @@ -106,17 +113,17 @@ func (p pom) listProperties(val reflect.Value) map[string]string { return props } -func (p pom) artifact() artifact { +func (p *pom) artifact() artifact { return newArtifact(p.content.GroupId, p.content.ArtifactId, p.content.Version, p.licenses(), p.content.Properties) } -func (p pom) licenses() []string { +func (p *pom) licenses() []string { return slices.ZeroToNil(lo.FilterMap(p.content.Licenses.License, func(lic pomLicense, _ int) (string, bool) { return lic.Name, lic.Name != "" })) } -func (p pom) repositories(servers []Server) ([]string, []string) { +func (p *pom) repositories(servers []Server) ([]string, []string) { logger := log.WithPrefix("pom") var releaseRepos, snapshotRepos []string for _, rep := range p.content.Repositories.Repository { @@ -242,9 +249,11 @@ func (d pomDependency) Resolve(props map[string]string, depManagement, rootDepMa if managed.Version != "" { dep.Version = evaluateVariable(managed.Version, props, nil) } + if managed.Scope != "" { dep.Scope = evaluateVariable(managed.Scope, props, nil) } + if managed.Optional { dep.Optional = managed.Optional } @@ -287,7 +296,7 @@ func (d pomDependency) ToArtifact(opts analysisOptions) artifact { } var locations ftypes.Locations - if opts.lineNumber { + if d.StartLine != 0 && d.EndLine != 0 { locations = ftypes.Locations{ { StartLine: d.StartLine, diff --git a/pkg/dependency/parser/java/pom/testdata/inherit-scopes-from-child-deps-and-their-parents/pom.xml b/pkg/dependency/parser/java/pom/testdata/inherit-scopes-from-child-deps-and-their-parents/pom.xml new file mode 100644 index 000000000000..17e6c55de090 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/inherit-scopes-from-child-deps-and-their-parents/pom.xml @@ -0,0 +1,29 @@ + + 4.0.0 + + com.example + inherit-scopes-from-child-deps-and-their-parents + 0.0.1 + + + + org.example + example-nested-scope-runtime + 1.0.0 + runtime + + + org.example + example-nested-scope-compile + 1.0.0 + compile + + + org.example + example-nested-scope-empty + 1.0.0 + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/inherit-scopes-in-children-from-root/pom.xml b/pkg/dependency/parser/java/pom/testdata/inherit-scopes-in-children-from-root/pom.xml new file mode 100644 index 000000000000..26bdb25621d7 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/inherit-scopes-in-children-from-root/pom.xml @@ -0,0 +1,64 @@ + + 4.0.0 + + com.example + inherit-scopes-in-children-from-root + 0.0.1 + + + + + org.example + example-scope-runtime + 2.0.0 + compile + + + org.example + example-scope-compile + 2.0.0 + runtime + + + org.example + example-api-compile + 3.0.0 + test + + + org.example + example-scope-empty + 2.0.0 + runtime + + + org.example + example-api-empty + 3.0.0 + test + + + + + + + org.example + example-nested-scope-runtime + 1.0.0 + runtime + + + org.example + example-nested-scope-compile + 1.0.0 + compile + + + org.example + example-nested-scope-empty + 1.0.0 + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/inherit-scopes-in-parents-from-root/parent/pom.xml b/pkg/dependency/parser/java/pom/testdata/inherit-scopes-in-parents-from-root/parent/pom.xml new file mode 100644 index 000000000000..dad0101e3f25 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/inherit-scopes-in-parents-from-root/parent/pom.xml @@ -0,0 +1,77 @@ + + 4.0.0 + + com.example + inherit-scopes-in-parents-from-root-parent + 0.0.1 + pom + + + + + org.example + example-scope-runtime + 2.0.0 + compile + + + org.example + example-scope-compile + 2.0.0 + runtime + + + org.example + example-api-compile + 3.0.0 + test + + + org.example + example-scope-empty + 2.0.0 + test + + + org.example + example-api-empty + 3.0.0 + test + + + org.example + example-nested-scope-compile + 1.0.0 + runtime + + + org.example + example-nested-scope-empty + 1.0.0 + compile + + + + + + + org.example + example-nested-scope-runtime + 1.0.0 + runtime + + + org.example + example-nested-scope-compile + 1.0.0 + compile + + + org.example + example-nested-scope-empty + 1.0.0 + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/inherit-scopes-in-parents-from-root/pom.xml b/pkg/dependency/parser/java/pom/testdata/inherit-scopes-in-parents-from-root/pom.xml new file mode 100644 index 000000000000..45ef37785e60 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/inherit-scopes-in-parents-from-root/pom.xml @@ -0,0 +1,39 @@ + + 4.0.0 + + com.example + inherit-scopes-in-parents-from-root + 0.1.0 + + + com.example + inherit-scopes-in-parents-from-root-parent + 0.0.1 + ./parent + + + + + + org.example + example-nested-scope-runtime + 1.0.0 + test + + + org.example + example-nested-scope-compile + 1.0.0 + test + + + org.example + example-nested-scope-empty + 1.0.0 + test + + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-compile/1.0.0/example-nested-scope-compile-1.0.0.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-compile/1.0.0/example-nested-scope-compile-1.0.0.pom new file mode 100644 index 000000000000..43ca478a88cd --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-compile/1.0.0/example-nested-scope-compile-1.0.0.pom @@ -0,0 +1,41 @@ + + + + 4.0.0 + + + com.example + example-nested-parent-scope-compile + 1.0.1 + ./parent + + + org.example + example-nested-scope-compile + 1.0.0 + + jar + Example pom with example-scope-compile 2.0.0 + + + + + org.example + example-api-compile + 3.0.0 + runtime + + + + + + + org.example + example-scope-compile + 2.0.0 + compile + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-compile/1.0.0/parent/pom.xml b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-compile/1.0.0/parent/pom.xml new file mode 100644 index 000000000000..5fe41b350df1 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-compile/1.0.0/parent/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + + com.example + example-nested-parent-scope-compile + 1.0.1 + pom + + + + + org.example + example-scope-compile + 2.0.0 + test + + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-empty/1.0.0/example-nested-scope-empty-1.0.0.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-empty/1.0.0/example-nested-scope-empty-1.0.0.pom new file mode 100644 index 000000000000..b1def2383f79 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-empty/1.0.0/example-nested-scope-empty-1.0.0.pom @@ -0,0 +1,40 @@ + + + + 4.0.0 + + + com.example + example-nested-parent-scope-empty + 1.0.1 + ./parent + + + org.example + example-nested-scope-empty + 1.0.0 + + jar + Example pom with example-scope-empty 2.0.0 + + + + + org.example + example-api-empty + 3.0.0 + test + + + + + + + org.example + example-scope-empty + 2.0.0 + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-empty/1.0.0/parent/pom.xml b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-empty/1.0.0/parent/pom.xml new file mode 100644 index 000000000000..6ca0232f4fc3 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-empty/1.0.0/parent/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + + com.example + example-nested-parent-scope-empty + 1.0.1 + pom + + + + + org.example + example-scope-empty + 2.0.0 + compile + + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-runtime/1.0.0/example-nested-scope-runtime-1.0.0.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-runtime/1.0.0/example-nested-scope-runtime-1.0.0.pom new file mode 100644 index 000000000000..8e48dcd812c3 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-runtime/1.0.0/example-nested-scope-runtime-1.0.0.pom @@ -0,0 +1,41 @@ + + + + 4.0.0 + + + com.example + example-nested-parent-scope-runtime + 1.0.1 + ./parent + + + org.example + example-nested-scope-runtime + 1.0.0 + + jar + Example pom with example-scope-runtime 2.0.0 + + + + + org.example + example-api-runtime + 3.0.0 + test + + + + + + + org.example + example-scope-runtime + 2.0.0 + runtime + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-runtime/1.0.0/parent/pom.xml b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-runtime/1.0.0/parent/pom.xml new file mode 100644 index 000000000000..f59df7902390 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-nested-scope-runtime/1.0.0/parent/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + + com.example + example-nested-parent-scope-runtime + 1.0.1 + pom + + + + + org.example + example-scope-runtime + 2.0.0 + test + + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-scope-compile/2.0.0/example-scope-compile-2.0.0.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-scope-compile/2.0.0/example-scope-compile-2.0.0.pom new file mode 100644 index 000000000000..1d5bc02ff3b9 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-scope-compile/2.0.0/example-scope-compile-2.0.0.pom @@ -0,0 +1,23 @@ + + + + 4.0.0 + + org.example + example-scope-compile + 2.0.0 + + jar + Example pom with example-api-compile 3.0.0 + + + + org.example + example-api-compile + 3.0.0 + compile + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-scope-empty/2.0.0/example-scope-empty-2.0.0.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-scope-empty/2.0.0/example-scope-empty-2.0.0.pom new file mode 100644 index 000000000000..82ce31008fc3 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-scope-empty/2.0.0/example-scope-empty-2.0.0.pom @@ -0,0 +1,22 @@ + + + + 4.0.0 + + org.example + example-scope-empty + 2.0.0 + + jar + Example pom with example-api-empty 3.0.0 + + + + org.example + example-api-empty + 3.0.0 + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-scope-runtime/2.0.0/example-scope-runtime-2.0.0.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-scope-runtime/2.0.0/example-scope-runtime-2.0.0.pom new file mode 100644 index 000000000000..90bf973def80 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-scope-runtime/2.0.0/example-scope-runtime-2.0.0.pom @@ -0,0 +1,23 @@ + + + + 4.0.0 + + org.example + example-scope-runtime + 2.0.0 + + jar + Example pom with example-api-runtime 3.0.0 + + + + org.example + example-api-runtime + 3.0.0 + runtime + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/transitive-parents/base/pom.xml b/pkg/dependency/parser/java/pom/testdata/transitive-parents/base/pom.xml index 5662c0c9e8de..ae1d6ced6e3f 100644 --- a/pkg/dependency/parser/java/pom/testdata/transitive-parents/base/pom.xml +++ b/pkg/dependency/parser/java/pom/testdata/transitive-parents/base/pom.xml @@ -13,7 +13,7 @@ com.example parent 3.0.0 - ../parent + ../ diff --git a/pkg/dependency/parser/java/pom/testdata/use-child-dep-management-in-parent/parent/pom.xml b/pkg/dependency/parser/java/pom/testdata/use-child-dep-management-in-parent/parent/pom.xml new file mode 100644 index 000000000000..dec887e6eecc --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/use-child-dep-management-in-parent/parent/pom.xml @@ -0,0 +1,34 @@ + + + + 4.0.0 + + + org.example + example-top-parent + 5.0.0 + ../top-parent/pom.xml + + + org.example + example-parent + 4.0.0 + pom + + + 4.0.1 + + + + org.example + example-api2 + + + org.example + example-api3 + 4.0.3 + compile + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/use-child-dep-management-in-parent/pom.xml b/pkg/dependency/parser/java/pom/testdata/use-child-dep-management-in-parent/pom.xml new file mode 100644 index 000000000000..651fbba9f885 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/use-child-dep-management-in-parent/pom.xml @@ -0,0 +1,45 @@ + + 4.0.0 + + + org.example + example-parent + 4.0.0 + ./parent/pom.xml + + + com.example + child-depManagement-in-parent + 1.0.0 + + + + 1.0.1 + 1.0.2 + 1.0.3 + + + + + + org.example + example-api + ${api.version} + runtime + + + org.example + example-api2 + ${api2.version} + runtime + + + org.example + example-api3 + ${api3.version} + runtime + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/use-child-dep-management-in-parent/top-parent/pom.xml b/pkg/dependency/parser/java/pom/testdata/use-child-dep-management-in-parent/top-parent/pom.xml new file mode 100644 index 000000000000..84d8954a4bf9 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/use-child-dep-management-in-parent/top-parent/pom.xml @@ -0,0 +1,34 @@ + + + + 4.0.0 + + org.example + example-top-parent + 5.0.0 + pom + + + 5.0.1 + + + + + org.example + example-api + ${api.version} + compile + + + org.example + example-api2 + + + org.example + example-api3 + 5.0.3 + compile + + + \ No newline at end of file