mirror of
https://github.com/aquasecurity/trivy.git
synced 2026-02-08 09:43:11 +08:00
fix(java): add hash of GAV+root pom file path for pkgID for packages from pom.xml files (#9880)
Co-authored-by: Teppei Fukuda <knqyf263@gmail.com>
This commit is contained in:
@@ -32,6 +32,11 @@ type artifact struct {
|
||||
Relationship ftypes.Relationship
|
||||
|
||||
Locations ftypes.Locations
|
||||
|
||||
// For correctly calculation package ID (hash),
|
||||
// We need to store the file paths for root or module artifacts.
|
||||
// For other artifacts, it will be empty.
|
||||
RootFilePath string
|
||||
}
|
||||
|
||||
func newArtifact(groupID, artifactID, version string, licenses []string, props map[string]string) artifact {
|
||||
|
||||
@@ -17,5 +17,5 @@ func (c pomCache) get(art artifact) *analysisResult {
|
||||
}
|
||||
|
||||
func (c pomCache) key(art artifact) string {
|
||||
return fmt.Sprintf("%s:%s", art.Name(), art.Version)
|
||||
return fmt.Sprintf("%s:%s:%s", art.Name(), art.Version, art.RootFilePath)
|
||||
}
|
||||
|
||||
@@ -13,9 +13,11 @@ import (
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/mitchellh/hashstructure/v2"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/net/html/charset"
|
||||
"golang.org/x/xerrors"
|
||||
@@ -124,7 +126,9 @@ func (p *Parser) Parse(ctx context.Context, r xio.ReadSeekerAt) ([]ftypes.Packag
|
||||
}
|
||||
|
||||
// Analyze root POM
|
||||
result, err := p.analyze(ctx, root, analysisOptions{})
|
||||
result, err := p.analyze(ctx, root, analysisOptions{
|
||||
rootFilePath: p.rootPath,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("analyze error (%s): %w", p.rootPath, err)
|
||||
}
|
||||
@@ -134,12 +138,17 @@ func (p *Parser) Parse(ctx context.Context, r xio.ReadSeekerAt) ([]ftypes.Packag
|
||||
|
||||
rootArt := root.artifact()
|
||||
rootArt.Relationship = ftypes.RelationshipRoot
|
||||
rootArt.RootFilePath = p.rootPath
|
||||
|
||||
return p.parseRoot(ctx, rootArt, set.New[string]())
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func (p *Parser) parseRoot(ctx context.Context, root artifact, uniqModules set.Set[string]) ([]ftypes.Package, []ftypes.Dependency, error) {
|
||||
if root.RootFilePath == "" {
|
||||
return nil, nil, xerrors.New("root file path is required for package ID generation")
|
||||
}
|
||||
|
||||
// Prepare a queue for dependencies
|
||||
queue := newArtifactQueue()
|
||||
|
||||
@@ -162,10 +171,11 @@ func (p *Parser) parseRoot(ctx context.Context, root artifact, uniqModules set.S
|
||||
// Modules should be handled separately so that they can have independent dependencies.
|
||||
// It means multi-module allows for duplicate dependencies.
|
||||
if art.Module {
|
||||
if uniqModules.Contains(art.String()) {
|
||||
id := packageID(art.Name(), art.Version.String(), art.RootFilePath)
|
||||
if uniqModules.Contains(id) {
|
||||
continue
|
||||
}
|
||||
uniqModules.Append(art.String())
|
||||
uniqModules.Append(id)
|
||||
|
||||
modulePkgs, moduleDeps, err := p.parseRoot(ctx, art, uniqModules)
|
||||
if err != nil {
|
||||
@@ -246,14 +256,14 @@ func (p *Parser) parseRoot(ctx context.Context, root artifact, uniqModules set.S
|
||||
dependsOn := xslices.Map(result.dependencies, func(a artifact) string {
|
||||
return a.Name()
|
||||
})
|
||||
uniqDeps[packageID(art.Name(), art.Version.String())] = dependsOn
|
||||
uniqDeps[packageID(art.Name(), art.Version.String(), root.RootFilePath)] = dependsOn
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to []ftypes.Package and []ftypes.Dependency
|
||||
for name, art := range uniqArtifacts {
|
||||
pkg := ftypes.Package{
|
||||
ID: packageID(name, art.Version.String()),
|
||||
ID: packageID(name, art.Version.String(), root.RootFilePath),
|
||||
Name: name,
|
||||
Version: art.Version.String(),
|
||||
Licenses: art.Licenses,
|
||||
@@ -265,7 +275,7 @@ func (p *Parser) parseRoot(ctx context.Context, root artifact, uniqModules set.S
|
||||
// Convert dependency names into dependency IDs
|
||||
dependsOn := lo.FilterMap(uniqDeps[pkg.ID], func(dependOnName string, _ int) (string, bool) {
|
||||
ver := depVersion(dependOnName, uniqArtifacts)
|
||||
return packageID(dependOnName, ver), ver != ""
|
||||
return packageID(dependOnName, ver, root.RootFilePath), ver != ""
|
||||
})
|
||||
|
||||
// `mvn` shows modules separately from the root package and does not show module nesting.
|
||||
@@ -304,13 +314,16 @@ func (p *Parser) parseModule(ctx context.Context, currentPath, relativePath stri
|
||||
return artifact{}, xerrors.Errorf("unable to open the relative path: %w", err)
|
||||
}
|
||||
|
||||
result, err := p.analyze(ctx, module, analysisOptions{})
|
||||
result, err := p.analyze(ctx, module, analysisOptions{
|
||||
rootFilePath: module.filePath,
|
||||
})
|
||||
if err != nil {
|
||||
return artifact{}, xerrors.Errorf("analyze error: %w", err)
|
||||
}
|
||||
|
||||
moduleArtifact := module.artifact()
|
||||
moduleArtifact.Module = true
|
||||
moduleArtifact.RootFilePath = module.filePath
|
||||
moduleArtifact.Relationship = ftypes.RelationshipWorkspace
|
||||
|
||||
p.cache.put(moduleArtifact, result)
|
||||
@@ -344,6 +357,7 @@ func (p *Parser) resolve(ctx context.Context, art artifact, rootDepManagement []
|
||||
result, err := p.analyze(ctx, pomContent, analysisOptions{
|
||||
exclusions: art.Exclusions,
|
||||
depManagement: rootDepManagement,
|
||||
rootFilePath: art.RootFilePath,
|
||||
})
|
||||
if err != nil {
|
||||
return analysisResult{}, xerrors.Errorf("analyze error: %w", err)
|
||||
@@ -365,6 +379,7 @@ type analysisResult struct {
|
||||
type analysisOptions struct {
|
||||
exclusions set.Set[string]
|
||||
depManagement []pomDependency // from the root POM
|
||||
rootFilePath string // File path of the root POM or module POM
|
||||
}
|
||||
|
||||
func (p *Parser) analyze(ctx context.Context, pom *pom, opts analysisOptions) (analysisResult, error) {
|
||||
@@ -394,9 +409,12 @@ func (p *Parser) analyze(ctx context.Context, pom *pom, opts analysisOptions) (a
|
||||
}
|
||||
deps = p.filterDependencies(deps, opts.exclusions)
|
||||
|
||||
art := pom.artifact()
|
||||
art.RootFilePath = opts.rootFilePath
|
||||
|
||||
return analysisResult{
|
||||
filePath: pom.filePath,
|
||||
artifact: pom.artifact(),
|
||||
artifact: art,
|
||||
dependencies: deps,
|
||||
dependencyManagement: depManagement,
|
||||
properties: props,
|
||||
@@ -876,8 +894,22 @@ func parseMavenMetadata(r io.Reader) (*Metadata, error) {
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func packageID(name, version string) string {
|
||||
return dependency.ID(ftypes.Pom, name, version)
|
||||
func packageID(name, version, pomFilePath string) string {
|
||||
gav := dependency.ID(ftypes.Pom, name, version)
|
||||
v := map[string]any{
|
||||
"gav": gav,
|
||||
"path": filepath.ToSlash(pomFilePath),
|
||||
}
|
||||
h, err := hashstructure.Hash(v, hashstructure.FormatV2, &hashstructure.HashOptions{
|
||||
ZeroNil: true,
|
||||
IgnoreZeroValue: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn("Failed to calculate hash", log.Err(err))
|
||||
return gav // fallback to GAV only
|
||||
}
|
||||
// Append 8-character hash suffix
|
||||
return fmt.Sprintf("%s::%s", gav, strconv.FormatUint(h, 16)[:8])
|
||||
}
|
||||
|
||||
// cf. https://github.com/apache/maven/blob/259404701402230299fe05ee889ecdf1c9dae816/maven-artifact/src/main/java/org/apache/maven/artifact/DefaultArtifact.java#L482-L486
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -285,6 +285,7 @@ func (d pomDependency) ToArtifact(opts analysisOptions) artifact {
|
||||
Exclusions: exclusions,
|
||||
Locations: locations,
|
||||
Relationship: ftypes.RelationshipIndirect, // default
|
||||
RootFilePath: opts.rootFilePath,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>module1</artifactId>
|
||||
<version>1.0.0</version>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>example-api</artifactId>
|
||||
<version>1.7.30</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>example-dependency</artifactId>
|
||||
<version>1.2.5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,26 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>module2</artifactId>
|
||||
<version>2.0.0</version>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>example-api</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>example-dependency</artifactId>
|
||||
<version>1.2.5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,16 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>root</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>module1</module>
|
||||
<module>module2</module>
|
||||
</modules>
|
||||
|
||||
|
||||
</project>
|
||||
16
pkg/dependency/parser/java/pom/testdata/multiple-modules-with-deps-with-same-gav/module1/pom.xml
vendored
Normal file
16
pkg/dependency/parser/java/pom/testdata/multiple-modules-with-deps-with-same-gav/module1/pom.xml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>module1</artifactId>
|
||||
<version>1.0.0</version>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>example-api</artifactId>
|
||||
<version>1.7.30</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
16
pkg/dependency/parser/java/pom/testdata/multiple-modules-with-deps-with-same-gav/module2/pom.xml
vendored
Normal file
16
pkg/dependency/parser/java/pom/testdata/multiple-modules-with-deps-with-same-gav/module2/pom.xml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>module2</artifactId>
|
||||
<version>2.0.0</version>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>example-api</artifactId>
|
||||
<version>1.7.30</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
16
pkg/dependency/parser/java/pom/testdata/multiple-modules-with-deps-with-same-gav/pom.xml
vendored
Normal file
16
pkg/dependency/parser/java/pom/testdata/multiple-modules-with-deps-with-same-gav/pom.xml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>root</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>module1</module>
|
||||
<module>module2</module>
|
||||
</modules>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>example-dependency</artifactId>
|
||||
<version>1.2.5</version>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
<name>Example API Dependency</name>
|
||||
<description>The example API</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>example-api</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
Reference in New Issue
Block a user