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