Skip to content

Fix Gradle scan crash and build impact paths per module - #190

Draft
Jordanh1996 wants to merge 6 commits into
masterfrom
fix/XRAY-100231-gradle-node-merge
Draft

Jordanh1996 wants to merge 6 commits into
masterfrom
fix/XRAY-100231-gradle-node-merge

Conversation

@Jordanh1996

@Jordanh1996 Jordanh1996 commented Sep 22, 2026

Copy link
Copy Markdown

Background

A Gradle build emits one dependency-tree file per module, and GradleTreeBuilder merges them into a
single map keyed by component ID — one slot per component for the whole project. When two modules
resolve the same component differently, that difference cannot be represented.

The merge used nodes.put, so the last file processed won. A transitive dependency reachable only
through the overwritten node lost its only parent edge, and the IDE plugin's impact-graph builder
then dereferenced a null parent set and aborted the entire scan:

Xray scan failed: NullPointerException: Cannot invoke "java.util.Set.iterator()"
  because the return value of "java.util.Map.get(Object)" is null
	at com.jfrog.ide.idea.scan.utils.ImpactTreeBuilder.walkParents(ImpactTreeBuilder.java:43)

jf audit on the same project succeeded, because the CLI keeps a tree per module rather than merging
them.

What changed

  • Merge shared nodes instead of replacing them, so no edge is ever dropped. This mirrors
    gradle-dep-tree's own GradleDependencyTreeUtils.addChild, which already merges. Scopes were
    silently overwritten the same way and are now unioned too.
  • DepTree carries the per-module trees alongside the merged map, and GradleTreeBuilder fills one
    entry per dependency-tree file. The merged map stays exactly as it was — createScanTree needs the
    union to decide what to send to Xray — while consumers that must respect module boundaries, such as
    impact-path construction, can now use the per-module trees.
  • DepTree keeps a two-argument constructor, so the npm, Yarn, Go and PyPI builders are untouched and
    leave the module list empty.

The companion change in jfrog-idea-plugin builds impact paths from these per-module trees. Without
it, a dependency excluded by one module is still shown as reachable through it.

How this was verified

  • New GradleTreeBuilderTest cases over a two-module fixture where modb excludes commons-lang3
    from commons-text and moda does not: the merged map keeps the edge, and the module trees stay
    distinct. Both fail without this change.
  • The four pre-existing GradleTreeBuilderTest cases still pass.
  • End to end in a real IntelliJ IDEA sandbox against a live Xray: before the change the scan died with
    the stack trace above; after it, the scan completes and the vulnerable transitive dependency is
    reported with its impact paths.

Notes for the reviewer

Two consequences of merging rather than replacing, both intended:

  • A component's scopes in the merged map is now the union across modules instead of whichever file
    happened to be read last. For multi-module Gradle projects this widens the IDE's scope filter, which
    is the correct behaviour — a dependency really is in every configuration its modules resolve it under.
  • computeIfAbsent also stops a later module's file from wiping descriptorFilePath off a module root
    that appears as a plain node elsewhere. Previously put erased it, and the module was then sent to
    Xray as if it were an external component.

MavenScanner.buildTree in jfrog-idea-plugin builds a synthetic root over one flat map the same
way, so multi-module Maven is theoretically exposed to the same thing. It is not the reported bug and
is not reproduced here; DepTreeModule is deliberately ecosystem-neutral so Maven can adopt it later.

About the red CI

The test matrix fails on every OS with the same three failures, all in
JfrogCliDriverTest: testRunAudit_MultiMavenProject, testRunAudit_NpmProject and
testRunAudit_WithExcludedPattern. They shell out to a real jf audit against a configured server
ID and need credentials the PR build does not have. This branch does not touch them, and master's
own test workflow has been failing since 30 April 2026.

Everything this change touches passes in CI, including both new tests —
gradleTreeBuilderModuleScopesTest, gradleTreeBuilderSharedDependencyTest, and the four
pre-existing GradleTreeBuilderTest cases.

Jordanh1996 and others added 4 commits September 22, 2026 20:10
A component that appears in more than one module's dependency-tree file may be
resolved there with different transitive dependencies. Replacing the node let the
last file win, which could leave a transitive dependency with no parent at all and
crash impact-graph construction.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The prior assertions on 'modb' were vacuously true whether or not
module scopes aliased the merged node map, since 'modb' never
resolves commons-lang3 in its own file. Assert on modb's own
commons-text node's children instead, which does regress if a
module's DepTreeNode is aliased to the merged map's node.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 22, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Comment @coderabbitai help to get the list of available commands.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Read each module's dependency-tree file into its own module tree first, then merge
those, so each step reads on its own. Fold the two shared-dependency tests into one
and drop a test that only restated the DepTree constructor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch is waiting to be deployed

1 waiting deployment
frogbot 2f536007 Waiting Sep 23, 2026 by Jordanh1996 via scan-pull-request #347
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant