Skip to content

Java impact: type initializers, library-run members, field initializer after a comment - #1880

Merged
swapnilpaliwal-sd merged 4 commits into
apps/integration-0.1.9from
fix/java-impact-loop
Oct 10, 2026
Merged

swapnilpaliwal-sd merged 4 commits into
apps/integration-0.1.9from
fix/java-impact-loop

Conversation

@swapnilpaliwal-sd

@swapnilpaliwal-sd swapnilpaliwal-sd commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Java impact --tests misses measured against a behavioural oracle on 12 Java (Maven, JUnit 4/5) repositories, 50 sampled methods each, every one broken on entry and the suite run (truth = test files that newly fail vs a per-test baseline). Dependencies and the JDK are staged as libraries. 8 repositories were used for tuning, 4 held out and scored once at the end.

Fixes

  1. A Java type's initializer is run by whoever uses the type (impact.dl load_hop, rung at load). A call written in a field initializer has no enclosing method, so the engine names the TYPE as its caller; a static / instance block is a <clinit> / <init_block> callable. Nothing calls either, so the upward walk stopped there: a private constructor called only from INSTANCE = new Factory() selected 0 of 17 failing test files. The hop now goes from such an initializer (a type reached as a caller, never a type/field target's own seed) to whoever uses the type: a call to one of its members, a construction of a subtype, a read or write of one of its fields. The SQL fast path declines such a closure.
  2. The at-load hop sees a test that reads a static field. KIND_SKIPS hands a method query empty field / faccess facts, so the field clause derived nothing (silently). Both are now loaded for method / param / var; every other rule joining them also needs a relation that stays skipped for those kinds.
  3. Parser: a field initializer written after a comment is read. static final F X = // why + newline + new F() { ... }; — the field extractor took the node after = as the initializer, and a comment is a named node, so the whole initializer (and an anonymous class it creates, with its methods) was dropped. Try-with-resources had the same read. Both now skip comments, as local variables already did.
  4. The members a library runs on a Java object reach whoever builds it (protocol rung). equals / hashCode / toString, and an override of a library method (iterator of an Iterable, size of a collection), are run by a HashMap, a StringBuilder, a for-each loop — never a written call on a client receiver, and the engine does not fan Object members on purpose. The protocol hop Python already has (whoever constructs the type) gains Java clauses: constructors of the type and its subtypes. Seed-only, as for Python.

Each fix has a case under tests/cases/java/ that fails on the base and passes here, with a control that must not change: type-initializer-runs-for-its-users (+ a type no test touches), field-initializer-after-a-comment (+ the same shape with no comment), library-runs-the-members-it-is-given (+ a type no test constructs). The parser comment-invariance twins gain the three initializer shapes (31 vs 36 rows before, equal after).

Numbers (base = integration tip)

set recall macro recall precision safe empty selections
tune (8) 0.930 → 0.982 0.909 → 0.936 0.181 → 0.183 291 → 302 / 340 16 → 10
held out (4) 0.946 → 0.992 0.948 → 0.973 0.105 → 0.105 172 → 182 / 187 5 → 5

An answer that errored counts as an empty selection (fix 3's subject had one mutant whose answer errored on the base: 71 failing files).
No subject loses recall. Per subject on tune, recall gains of +0.25, +0.15, +0.12 come from fixes 1–2, 3–4 and 1 respectively.
path (mutation pairs) 0.849 → 0.853, runtime direct callers 0.754 → 0.754 (585 targets), runtime A→C paths 399/442 unchanged, context recall@5 0.568 unchanged.
Rung precision on tune: at load 0.46 (n=67), protocol 0.10 (n=898, the weakest rung, kept below one of a set).

Cost, warm, interleaved A B A B on two subjects: index 24.5/30.3 s → 28.8/25.7 s and 43.4/41.1 s → 42.1/42.2 s (noise); 20 impact queries 16.4/17.4 s → 22.0/19.4 s and 74.6/71.5 s → 78.9/76.1 s (+6 to +25%, the field facts now loaded for a method query).

Suites

tests/run.py: java 343/343, typescript 269/269, javascript 307/307, python 306/306. Java engine suite with --oracle (JDK 25): 79 passed, 0 failed, on base and on the parser change; torture SKIPs on this machine (its golden is pinned to another platform IR).

Still open (fewer than two repositories, or precision only)

  • framework-run members of a web application (controller constructors, binder initialisers, template getters) — one repository
  • a callback held by a library object (a cache loader, a class-value computation) — one repository
  • assertThat(x.primitiveGetter()).isEqualTo(..): a call argument returning a primitive keeps every overload, so the chained assertion stays unresolved (three repositories; feeds by name rows)
  • an implicit super() to a base no-arg constructor is not an edge
  • path does not take the at-load hop

swapnilpaliwal-sd and others added 4 commits October 9, 2026 14:32
A call written in a field initializer has no method around it, so the engine names the
type as its caller, and a static or instance block is a <clinit> / <init_block> callable.
Nothing calls either, so the upward walk stopped there: a factory whose
`INSTANCE = new Factory()` throws breaks every test that touches the factory, and none of
them was selected.

impact.dl now hops from such an initializer (a type reached as a caller, or a <clinit> /
<init_block>) to whoever uses the type in a way that runs it: a call to one of its
members, a construction of a subtype, a read or write of one of its fields. The hop is
taken only where the walk arrives at the type as a caller, never from a type or field
target's own seed. Test routes through it carry a new rung, `at load`, ranked beside
`at import`. The SQL fast path declines a closure that reaches such an initializer.

Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
`static final F X = // why
     new F() { ... };` — the Java field extractor took the node right after `=` as the
initializer, and a comment is a named node in tree-sitter, so the whole initializer was
dropped: its calls, and an anonymous class it creates together with that class's methods.
A try-with-resources declaration had the same read. Both now skip comments, as a local
variable's initializer already did.

The comment-invariance twins gain a field initializer, a field holding an anonymous class
and a resource declaration; the case checks that a test reaching the anonymous class's
method through its interface is selected, beside the same shape with no comment.

Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
A method query is handed the field and field-access facts empty (KIND_SKIPS: only a field or
type target was thought to reach them), so the `at load` hop's field clause derived nothing
and a test that touches the initialized type only by reading one of its static fields
(`Factory.INSTANCE_NULL.lookup(...)`) was dropped. Both relations are now loaded for the
kinds that walk the closure; every other rule that joins them also needs a relation that
stays skipped for those kinds, so nothing else changes. The case gains that shape.

Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…lds it

`equals`, `hashCode` and `toString` are run by a HashMap key, a StringBuilder, String.format,
an assertion; an override of a library method (`iterator` of an Iterable, `size` of a
Collection, `close`) by the for-each loop, the collection, try-with-resources. The call is
written on a library receiver or not at all, and Object's members are deliberately not
fanned to client overrides by the engine, so a change to one had no caller and selected no
test.

The protocol hop Python already has (whoever constructs the type runs its protocol
members) now has Java clauses: a member named equals / hashCode / toString, or one that
overrides a library method, is reached by the callers of the type's constructors and of its
subtypes' constructors. Still taken only from the change itself, under the `protocol` rung.
The SQL fast path declines such a target.

Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
@swapnilpaliwal-sd
swapnilpaliwal-sd merged commit 76b4292 into apps/integration-0.1.9 Oct 10, 2026
12 checks passed
@swapnilpaliwal-sd
swapnilpaliwal-sd deleted the fix/java-impact-loop branch October 10, 2026 03:48
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