Skip to content

feat(java): take up codeanalyzer-java 3.1.0 — config-read tiers and the entrypoint report - #371

Merged
rahlk merged 3 commits into
release/2.0from
feat/issue-369-canj-310
Sep 8, 2026
Merged

feat(java): take up codeanalyzer-java 3.1.0 — config-read tiers and the entrypoint report#371
rahlk merged 3 commits into
release/2.0from
feat/issue-369-canj-310

Conversation

@rahlk

@rahlk rahlk commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Closes #369.

codeanalyzer-java 3.1.0 adds config-read tiers (upstream #233, #237) and the entrypoint report (#235). get_config_readers and get_unresolved_config_reads stop returning [] unconditionally, and get_entrypoint_coverage stops reporting the report unavailable.

The models were the task

A 3.1.0 payload failed JApplication validation with 1,368 errors across five fields — entrypoint_frameworks on every JCallable and JType, plus entrypoint_report / config_uses / config_reads_unresolved on the application root. extra="forbid" is why that surfaced as a loud failure instead of silently dropped data.

Added JEntrypointReport, JConfigUse, JConfigRead, mirroring the wire — which spells them exactly as Python's PyApplication already does.

The four accessors are implemented once on JavaAnalysisBackend over the canonical JApplication, which JNeo4jBackend now rebuilds with the overlays in it; both per-backend return [] stubs are gone. prov surfaces the tier rather than flattening it, so a caller can tell a literal read at the call site from one inferred over the DDG.

The refusal probe is not the config layer's absence

The obvious probe is wrong in both directions, and I measured why:

  • The analyzer writes config_uses / config_reads_unresolved only when non-empty — the a4 fixture is a real 3.1.0 level-4 run over a tree that reads no configuration, and carries neither key.
  • The graph declares the two relationship types only once an edge exists.

Either would refuse a clean 3.1.0 analysis that genuinely reads nothing — an ambiguous empty of exactly the kind this closes. The witness used instead is the sibling overlay from the same release: 3.1.0 writes an entrypoint report on every run; 3.0.x writes none of the three. One probe, no version literal, both directions pinned.

Verified, not assumed

  • Relationship spellings: no parity violation. Read off a graph emitted with 3.1.0: J_USES_CONFIG and J_READS_CONFIG_UNRESOLVED. The JAVA_ prefix mentioned on codeanalyzer-java#231 did not ship. Agrees with PY_* and TS_* on the concept, differing only by each language's own node prefix.
  • Comments are still blocked. 3.1.0 closes two of TSExternalSymbol has multiple versions #231's three asks: :JComment is a declared label with zero nodes, and only a declaration's docstring reaches the graph. remove_all_comments stays in RAISING and the file-keyed Neo4j comment accessors keep refusing, recorded as a standing limitation.

Upstream regression found and filed — codeanalyzer-java#240

3.1.0 makes the level-4 points-to tier depend on --external-calls, which was unconditional before:

run ddg points-to
3.0.3 -a 4 10,430 1,134
3.1.0 -a 4 10,154 858
3.1.0 -a 4 --external-calls 10,430 1,134

--emit neo4j forces the flag and the in-process backend never passes it, so the graph is now a strict superset of the payload by 276 edges and the same slice reaches further over Neo4j. Three live tests moved from equality to containment plus exact size plus all-points-to. Measured and recorded rather than worked around.

One contract test carries an argued exemption

test_generic_methods_are_all_abstract_on_the_java_contract requires every generic-ABC method to stay abstract, which conflicts with answering the config layer once for both backends. Exactly those two are excluded by name, and a new test asserts by identity that neither backend overrides them — a more direct guarantee of the property that matters than "is abstract" was, and one that goes stale loudly if the design changes.

Verification

Full gate 1584 passed, 367 skipped, 85.27% (baseline 1571 / 367 / 85.22%). Java live against the re-emitted two-application 3.1.0 graph with every gate satisfied: 728 passed, 2 skipped. Both fixtures regenerated with every published README figure re-measured — nothing moved but the version stamp and the additive fields.

rahlk and others added 3 commits September 7, 2026 19:33
…he entrypoint report

3.1.0 adds three application-scope overlays: the entrypoint pass's coverage
record (codeanalyzer-java#235) and the code-to-config layer in two tiers, a
literal tier (#233) and a dataflow tier closed over the L3 DDG and the L4 call
graph (#237). The graph contract stays at 2.0.0, but the Java models are
extra="forbid", so a 3.1.0 payload did not parse at all until they grew the
five new fields.

Models mirror the wire: JEntrypointReport, JConfigUse and JConfigRead (the last
two are PyConfigUseEdge/PyConfigRead's fields verbatim), entrypoint_frameworks
on JCallable and JType, and the three overlays on JApplication as Optional —
the one place in that file where None and [] are different answers.

get_config_uses, get_unresolved_config_reads, get_config_readers and
get_entrypoint_coverage are implemented once on JavaAnalysisBackend over the
canonical JApplication, which JNeo4jBackend now rebuilds with the overlays in
it (two more statements, and only on a graph that carries them). The two tiers
are surfaced through prov rather than flattened: a key reached over dataflow is
weaker evidence than a literal at the call site.

The refusal is kept and measured from the data. It is deliberately NOT the
config layer's own absence, which is ambiguous both ways: the analyzer writes
config_uses/config_reads_unresolved only when non-empty (the a4 fixture is a
level-4 3.1.0 run that reads no configuration and carries neither key), and the
graph declares J_USES_CONFIG/J_READS_CONFIG_UNRESOLVED only once an edge
exists. The witness is the sibling overlay from the same release — 3.1.0 writes
an entrypoint report on every run, 3.0.x writes none of the three — so all three
are probed as the one analyzer generation they are, and no version literal
enters the decision path.

Verified rather than assumed, against a graph emitted by 3.1.0:
- The relationship names are J_USES_CONFIG and J_READS_CONFIG_UNRESOLVED, not
  the JAVA_ prefix a comment on #231 proposed. That agrees with PY_/TS_ on the
  concept and differs only in each language's own node prefix, so there is no
  parity-clause problem to settle upstream.
- The projection still carries no comment nodes: :JComment is a declared label
  with zero nodes. remove_all_comments and the file-keyed Neo4j comment
  accessors stay blocked and stay documented as such.

Fixtures regenerated with 3.1.0 and every published figure in their README
re-measured: both files are identical to their 3.0.3 copies apart from the
version stamp and the additive fields, and no figure moved.

Closes #369
…he uptake moved

7691 re-emitted with codeanalyzer-java 3.1.0, both applications
(daytrader8 18,354 nodes, thingsboard) so the graph stays the two-application
scope audit. Three live figures moved, each re-measured rather than adjusted:

- SCALE_EXTERNALS 2,570 -> 2,571. The new :JExternal is not a call target:
  J_READS_CONFIG_UNRESOLVED points at a ghost, so ThingsBoard's @value reads
  mint @external/org.springframework.beans.factory.annotation.Value/value()
  where nothing calls it.
- The whole-database ssa DDG count 324,959 -> 324,952, all seven in ThingsBoard.
- get_ddg over Neo4j now carries 276 points-to edges on daytrader8 that the
  same analyzer's analysis.json does not — and it is a difference of what each
  source was asked, not of the projection. codeanalyzer-java 3.1.0 makes the
  level-4 points-to layer depend on --external-calls, which --emit neo4j forces
  on and which the SDK's local run does not pass; 3.0.3 produced the same edges
  either way. Four runs of the same tree: 3.0.3 -a 4 -> 10,430 (1,134
  points-to); 3.1.0 -a 4 -> 10,154 (858); 3.1.0 -a 4 --external-calls ->
  10,430, set for set identical to the graph; 3.1.0 --emit neo4j -> 10,430.
  The graph is a strict superset and the payload has nothing the graph lacks,
  so the three affected tests assert the containment, its exact size and that
  every edge in the gap is points-to — a regression in either direction still
  fails, and fails naming what it is. Worth reporting upstream: the flag is
  documented as controlling only whether out-of-project call targets are homed
  as external_symbols.

Also fixes a refusal that could be skipped: get_config_readers short-circuited
on a key the application does not declare, so it answered [] on a 3.0.x
analysis where every sibling raised. It now goes through get_config_uses, and
the refusal test covers the undeclared-key case.
…-canj-310

# Conflicts:
#	CHANGELOG.md
#	docs/agent-api-reference.md
@rahlk
rahlk merged commit 9325591 into release/2.0 Sep 8, 2026
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