fix: link nested java packages to their enclosing jar when nested cataloging is on

the legacy recursion links a nested jar to the jar that holds it: a `DEPENDENCY_OF` edge from inner to outer, and `JavaArchive.Parent` on the inner package. Inside an extracted archive java called `parse(ctx, nil)`, so both went away as soon as the feature was on. What replaced them was `file CONTAINS package`, which SPDX and CycloneDX drop, so those formats lost all war-to-jar structure.

- `archive.Traversal` gains `Parent` and `Packages` (the packages found describing that archive, filled in by the task after its sub-pipeline runs)
- java picks the enclosing jar's main package from the parent traversal (the one whose virtual path is the archive's own; pom-derived aux packages carry a suffixed one) and passes it to `parse`, then sets `Parent` the same way the legacy path does

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Alex Goodman
2026-09-24 13:36:52 -04:00
parent 23de18918e
commit 22edfe66ca
4 changed files with 88 additions and 2 deletions
+13 -1
View File
@@ -203,7 +203,7 @@ func (c *archiveCataloger) processArchive(ctx context.Context, parentResolver fi
// extracted files keep the filesystem ID of where the archive was found; the nesting chain is
// carried separately as the archive path. The chain names real paths, not the link the archive was
// reached by, so it is stable and joins to the archive's package location by prefix.
traversal := &archive.Traversal{Location: location}
traversal := &archive.Traversal{Location: location, Parent: archive.TraversalFromContext(ctx)}
archivePath := traversal.ContentsArchivePath()
extracted, err := archive.Extract(ctx, archiveContent, location.FileSystemID, archivePath, c.limiter, c.exclusions)
if errors.Is(err, archive.ErrDiskLimitReached) {
@@ -240,6 +240,7 @@ func (c *archiveCataloger) processArchive(ctx context.Context, parentResolver fi
c.progress.Increment()
scratch := c.runSubPipeline(ctx, extracted, location.Coordinates)
traversal.Packages = packagesAt(scratch, location.Coordinates)
children := c.discoverArchives(extracted)
mergeArchiveResults(location.Coordinates, archivePath, children, scratch, builder)
c.progress.AtomicStage.Set(fmt.Sprintf("%s archives (%s)", humanize.Comma(c.progress.Current()), archivePath))
@@ -277,6 +278,17 @@ func (c *archiveCataloger) isAncestor(digests []file.Digest) bool {
return false
}
// packagesAt returns the packages located at the given coordinates, i.e. those describing the file there.
func packagesAt(s *sbom.SBOM, coordinates file.Coordinates) []pkg.Package {
var out []pkg.Package
for _, p := range s.Artifacts.Packages.Sorted() {
if p.Locations.CoordinateSet().Contains(coordinates) {
out = append(out, p)
}
}
return out
}
func recordArchiveUnknown(builder sbomsync.Builder, coordinates file.Coordinates, reason string) {
appendUnknowns(builder, ArchiveCatalogerTaskName, []unknown.CoordinateError{{Coordinates: coordinates, Reason: errors.New(reason)}})
}