Repository navigation
Conversation
…tiple_of) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
Parallel scaling meets Kotlin: companion members are appended to a class's member lists through new StructClass mutators, which read the lazy lists first. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
…age flag main's global --image NAME (and --image=NAME) selects one image of a dyld shared cache. On a JVM input the engine refuses it, in either spelling and on either side of the input operand, with the native-flag message (exit 2); a native input behaves as on main. A test next to the other refused-flag tests pins that, and docs/cli.md's --image section says so, as its --slice section does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
…the final build The numbers predated the merge of the two performance branches. Measured with decompile-project on input-full.jar, best of three, 80 cores, load average 2-22 (mostly the runs themselves), 40 GB scope: --jobs 1 / 8 / 24 (auto): Elasticsearch 190 / 27 / 12.5 s, peaks 2.5 / 4.2 / 7.4 GB; Guava 20 / 3.4 / 1.9 s, 0.37 / 0.74 / 1.5 GB; spring-boot 0.16 / 0.31 / 0.67 GB; Ghidra 0.82 / 1.4 GB at 8 / 24. The memory estimate still bounds every peak. The speed paragraph compared a 3-8x slowdown from when the port first reached parity; one instance now takes 1.2-2.2x Vineflower's -thr=1 wall time (spring-boot 7.2 vs 6.1 s, Guava 20 vs 13, spring-framework 38 vs 21, Elasticsearch 190 vs 85), and with both tools' default flags kuna is faster on all four with a third of the CPU time or less. Elasticsearch output is identical at 1, 8 and 24 workers. kuna_reclaim joins the list of kuna's own modules, and docs/cli.md's kuna test --jvm summary line shows the 607-case corpus. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
… inflation and the heap policy Three behaviours the performance branches shipped had no prose in the live spec: kuna_reclaim's per-method windows and per-class releases (10.13), inflating a large archive's class entries on up to eight threads (10.2), and the glibc heap policy the CLI sets before a JVM decompile (10.14). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
…oracle The scripts and driver sources that build the Java and Kotlin oracle corpora tests/jvm is cut from: fetch (pinned hashes), compile, manifest, oracle run, validation and cross-checks, with every download and output under KUNA_JVM_ORACLE instead of a home directory. No data, jars or JDKs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
…mark.md The DecBench-style Java benchmark that compares kuna's JVM engine with Vineflower 1.12.0 on five large projects, as a scripts/ package: every path under one workspace (--workspace or JAVABENCH_WORKSPACE), the dataset's pinned SHA-256s shipped as pins/, the metric tests as unittest cases that skip without tree-sitter or a JDK, and a standard-library test of the workspace convention in make test-tools. docs/jvm-benchmark.md states the result: identical scores on all 110,238 methods, timings of one back-to-back run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
…directory defaults Every script, test helper and README that located the Vineflower jar, the JDKs, kotlinc, the oracle corpora or their tools under a home directory now defaults to the oracle workspace named by KUNA_JVM_ORACLE (tools/oracle/README.md), keeps its per-script overrides, and stops with a one-line message naming the variable when it is unset. The Kotlin real-world jars come from the oracle's pinned Maven Central downloads, vf-decompile from tools/oracle. f6_support's jar lookup reads VINEFLOWER_JAR, then KUNA_JVM_ORACLE, and tests still skip without a jar. tests/jvm/README.md and kotlin.json are regenerated; every other file under tests/jvm regenerates byte for byte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
…-image refusal The crate header says the engine also writes Kotlin and names the three bundled plugins; the estimate_memory comment points at docs/jvm.md instead of quoting superseded peaks; a JVM input refuses --image=X by naming the flag --image (the test pins it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
… scoring modules report and compare resolved their run directories only after importing the scoring stack, so on a machine without tree-sitter the missing-run error was an import traceback. The Java parser is now loaded on first use. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
…h a helper The b19 oracle generator reproduces golden.txt byte for byte. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
…t two fixes The engine's contract was Vineflower 1.12.0's exact text, failures included. kuna_fixes adds the mechanism to fix Vineflower's defects in kuna's own engine: one enum variant and one jvm_fix_<slug> row (default on) per fix, kuna_fixes::enabled() at every site that departs from the Java, and jvm_vineflower_compat (default off) to turn every fix off at once. Fernflower::new takes the switches off the property list into the context; installing a context makes its set the one the gate answers from. The corpus runner turns compat on unless a case says otherwise, so the 607 Vineflower-oracle cases keep guarding the port. Fixes: jvm_fix_report_to_kuna (the failure comment's default note names kuna's tracker) and jvm_fix_lambda_wildcard_return (NewExprent's guard from upstream 13d4112e dereferenced the null return type of a method reference on a Foo<?> receiver; the method now keeps the inferred mapping). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
A kuna-fixes case is a small class Vineflower 1.12.0 fails on. regen.py appends it to cases.tsv twice: kuna-fixes-compat/<id> (jvm_vineflower_compat on, expected = the release jar's text through vf-decompile --fixture) and kuna-fixes/<id> (defaults, expected = kuna's reviewed output), and writes kuna-fixes/README.md with the diff between the two. kuna_fix_case.py adds a case from a class file end to end. regen.py still reproduces every other vendored file byte for byte. First case: pkg/TestMethodRefWildcardReceiver for jvm_fix_lambda_wildcard_return; both runs are in baseline.txt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
CI lints with the current stable toolchain. Three more structural lints join the port's allow list (collapsible_match, while_let_loop, question_mark); the jimage directory reader uses as_chunks. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
…diff checker, CLI test - rt/exception.rs: KUNA_JVM_BACKTRACE=<text>|1 prints the Rust backtrace of a caught engine throw (the hook swallows them otherwise). - examples/jvmtrace.rs: the trace differ's kuna side runs in compat mode, since it is compared with Vineflower's trace. - tests/jvm/kuna_fix_diff.py: checks that two decompile-project trees (compat and default) differ only inside methods that failed in compat mode, in tracker notes or in imports. - jvm_fix_report_to_kuna's row names every error comment it applies to; the JVM catalog preamble mentions the fixes and the compat switch. - kuna-cli: the fix is on by default, compat and the per-fix option turn it off, the note names the right tracker, a bad switch value exits 2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
docs/jvm.md: the fidelity contract now has two modes (compat: Vineflower's exact text; default: the same except inside the methods kuna fixes and the tracker note), a Kuna-only fixes section with the list of fixes, and the recipe every fix follows (reproduce, diagnose in the Java, gate, case, spec, evidence, gates). docs/spec/10-jvm.md 10.16: the gate, the compat switch, one paragraph per fix. docs/cli.md, docs/jvm-benchmark.md: the contract statements. docs/options-jvm.md regenerated (73 options). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
Root cause: TryWithResourcesProcessor.makeTryWithResourceJ11 (Vineflower
1.12.0 :166, unchanged on develop/1.13.0) recognizes a Java 11+
try-with-resources from its catch (Throwable) handler. When the handler is a
sequence starting with an if, it strips the negations off the condition and
takes the result for the resource's null check, reading operand 0 (must be a
variable) and operand 1 (must be null). A hand-written handler starting with
if (!flag) on a boolean local leaves a one-operand BOOL_NOT whose operand is a
variable, so operand 1 does not exist and the method fails with an index out
of bounds (spring-framework ConfigurationClassEnhancer.createClass). With the
fix, a condition with fewer than two operands is not a null check and the
matcher leaves the statement alone, as it does when the operand is not a
variable. The guard fires only where Vineflower always throws.
Evidence:
(b) pkg/TestTryCatchNotFlag (javac 17.0.20.1): the default output recompiles
and javap -c -p of both classes is identical to the original;
ConfigurationClassEnhancer recompiled (javac 21 --release 17): the outer
class's javap -c -p, createClass included, is identical to the original.
(c) jvmdec --jobs 16, compat vs default, tests/jvm/kuna_fix_diff.py: 0 other
hunks on guava, spring-boot, spring-framework (1 fixed: createClass),
ghidra, elasticsearch, okio, okhttp, kotlin-stdlib.
(d) corpus 611/611, 0 regressions; cargo test -p kuna-jvm 484 passed,
0 failed, 3 ignored; check_spec --strict OK; clippy 1.98.1 clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
kuna_fix_case.py and regen.py handled Java cases only (.java texts, no
plugins). A case now has a language, picked per case and Java by default:
a Kotlin case ("language": "kotlin" in kuna-fixes/cases.json; the
script's --language, defaulting to kotlin for a class with kotlin.Metadata)
gets its oracle text from vf-decompile --plugins, runs with jvm_plugins=1,
stores <id>.kt in expected-compat/ and expected/, and compares <Stem>.kt
(falling back to <Stem>.java, as the vendored Kotlin cases do). Java cases
and the rest of the corpus regenerate byte for byte; the kuna-fixes README
gains one sentence. No runner change: per-case opt lines already overlay.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
IfHelper.ifElseChainDenesting rewrites `if (a) { if (b) X; ...; Y } Z` as
`if (!a) { Z } else { ... }` once the body has *some* jump to the end of the
enclosing sequence (hasDirectEndEdge: a catch's return, a finally handler's
rethrow edge). Unlike reorderIf before the same swapBranches, it does not
check that no jump from inside the if reaches the statements it moves, so a
body that also falls through to Z keeps break edges that now enter the
then-branch from outside (ValidationHelper: "Break edge with closure with
invalid direct path", right after the swap, in both engines). Upstream:
introduced in 91ff9353 (#69), unchanged on develop/1.13.0.
Effects in Vineflower: Z a lone return -> CondenseExits copies it, output
correct but restructured; otherwise a `break` past Z (skipped statement,
missing return: wrong code); with an assert before the try, AssertProcessor
moves the stale edge onto the assert block, Z is emptied and dropped, and
VarDefinitionHelper fails with "Could not find destination nodes for stat id"
(Elasticsearch InternalEngine.closeNoLock).
Fix: skip the swap when an edge from inside the if reaches a statement after
it in the sequence (KunaFix::IfDenestFallthrough, option row, gate in
if_helper.rs; Java kept verbatim under compat).
Evidence:
(a) pass trace (vftrace) identical to Vineflower's on the case; both engines'
ValidationHelper fail every changed method at the validate call after
ifElseChainDenesting (IfHelper.java:104 in the release jar); with the fix
no validation error.
(b) case pkg/TestIfDenestFallthrough (javac 17.0.20.1 -g): default output
recompiles, `javap -c -p` identical to the original class; the compat
text does not compile (missing return). closeNoLock recompiled with the
benchmark's javac/classpath: bytecode exact (169 insns).
(c) jvmdec compat vs default, kuna_fix_diff.py: guava, spring-boot,
spring-framework, ghidra, elasticsearch (input-full and classes.jar),
okio, okhttp, kotlin-stdlib, kotlinx-coroutines: differences only in
closeNoLock (fixed, + its import) and two methods Vineflower printed via
the healed path: ghidra DataTypeManagerDB.resolveBuiltIn (bytes 0.56 ->
exact) and spring AnnotationTypeFilter.hasAnnotation (0.92 -> exact),
both now the source's shape; plus tracker notes.
(d) corpus gate 611/611, 0 regressions; cargo test -p kuna-jvm 485 passed,
3 ignored; check_spec --strict OK; clippy 1.98.1 -D warnings clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
Vineflower 1.12.0 SwitchExprent.toJava gives a constant yielded by a single-expression arm the switch expression's type. That type is the join of the arms' values (VarType.join -> findCommonAncestor), so when a string or class literal sits next to an arm of an unrelated reference type (a boxed char/int/boolean, a StringBuilder, a Class) the literal is retyped to Serializable (Object without the JDK) and ConstExprent.toJava throws "invalid constant type". Vineflower guards the same retype in VarDefinitionHelper (canConstTypeMerge, aea5fd12) but not here; still present on develop/1.13.0, and the 1.13.0-dev jar fails the same way. With the fix the constant takes the switch's type only when ConstExprent::can_be_written_as accepts it (exactly the types to_java writes: a primitive or its box, null, String, Class, MethodType); otherwise it keeps its own type, a subtype of the join that the switch's assignment/invocation context widens as javac compiled it. Case labels are not touched (crash/C19 is a different defect). Evidence: (b) reproducer pkg/TestSwitchExprConstLub (javac 17.0.20.1, -source/-target 17 -g): 8 methods fail in compat mode (as with the release jar), 0 by default; the default output recompiles and javap -c of all 18 methods matches the original, also for -g:none and with the JDK 21 runtime loaded (join = Serializable). spring-framework BasicJsonWriter.escape decompiles; recompiled, escape is identical and lambda$escape$0 differs only in the layout order of its eight case bodies (same key -> constant mapping). (c) jvmdec compat vs default, kuna_fix_diff.py: guava, ghidra, spring-boot, elasticsearch, okio, okhttp, stdlib: only tracker notes and the existing fix; spring- framework: 1 method fixed, its one hunk re-wraps the enclosing input.chars().forEach(...) statement of the failing lambda (inside escape). (d) corpus 611/611, 0 regressions; cargo test -p kuna-jvm 486 passed, 3 ignored; check_spec --strict OK; clippy 1.98.1 clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
… merging two fixes Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
Unchanged copy of ccc5b5c (branch jvm/fix-kwriter), so that the Kotlin two-pass cases of this branch use the same harness: a case has a language; a Kotlin case gets its oracle text from vf-decompile --plugins, runs with jvm_plugins=1, stores <id>.kt and compares <Stem>.kt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
# Conflicts: # decompiler/nonnative/kuna-jvm/options.toml # tests/jvm/README.md # tests/jvm/cases.tsv # tests/jvm/kuna-fixes/README.md # tests/jvm/kuna-fixes/cases.json
A Kotlin primary constructor whose supertype call leaves out defaulted
arguments calls the superclass's synthetic default-arguments constructor
<init>(P1..Pn, int mask.., DefaultConstructorMarker). Vineflower's
KConstructor.writePrimaryConstructor removes only the marker
(KUtils.removeArguments), so the call names <init>(P1..Pn, int), which
does not exist: when the superclass is decompiled in the same run and its
wrapper is still alive, ExprUtil.getSyntheticParametersMask throws
"Constructor ... not found" outside any method guard and the whole class
fails (kotlinx-coroutines 1.8.0/1.10.2 BroadcastChannelImpl, kotlin-reflect
CallerImpl); otherwise the placeholders and mask are printed
(": Task(name, false, 2)"). Unchanged on develop/1.13.0.
The fix rewrites the call into the call it stands for, <init>(P1..Pn) with
the trailing defaulted arguments left out, when the shape, the constant
masks and placeholders, the synthetic constructor, the superclass's
metadata (a default declared for every left-out parameter) and the absence
of a competing constructor all confirm it; anything else keeps the Java.
Evidence: reproducer pkg/TestDefaultArgsSuperCall (kotlinc 2.0.0) fails 3
methods in compat exactly as vineflower-1.12.0.jar, 1 by default (the
competing-constructor shape, by design); a nested-class form recompiled
with kotlinc gives identical javap -c -p for every subclass. Whole-class
failures, single instance, compat -> default: coroutines 1.8.0 1 -> 0,
1.10.2 1 -> 0, kotlin-reflect 2.3.21 1 -> 0 (CallerImpl). Compat vs default
(jvmdec --jobs 12) on guava, spring-boot, spring-framework, ghidra,
elasticsearch, okio: only failed methods and tracker notes; outside them
exactly the same construct: okhttp RealWebSocket.WriterTask and stdlib
TerminateException now read Task("$name writer") / FileSystemException(file)
as in their sources.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0179iMKReqhJewXF589wZswF
…nd compound forms source_scopes now accepts several split-off variables renamed to one local (var2..var10 -> h in ShardRouting.hashCode, var15..var17 -> h1 in MurmurHash3) and expands x op= e (shifts too) with parentheses unless e uses only the same associative operator. Base vs HEAD default output on the five Java jars, every hunk classified: 8 left unrecognised (SleighCompile's do-while, BlendedTermQuery x3, AlpDoubleUtils, TriangleTreeWriter, Iso8601Parser), all read: equivalent, the source's statements. test_kuna_fix_diff: 137 tests OK; kuna_fix_check OK. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
…exits, guards two Vineflower exits, adds TestJavacTails Continues the WIP commit c693078 (tails after loops and switches, for headers from the LocalVariableTable and LineNumberTable, value returns kept out of try/synchronized, if (a || b) continue, early exits in try-with-resources, finally and synchronized blocks): - copied_exit_start reads the offset of a rebuilt block only when the block is the exit alone (ExitHelper's copy); a rebuilt branch that ends with the method's shared return keeps Vineflower's if-else (ES AbstractHttpServerTransport.dispatchRequest). - LoopExtractHelper::extract_first_if no longer makes a loop's first if whose other path continues the loop that loop's exit (else_continues). With a tail moved after an outer loop the climb to a regular successor found one and printed `if (cmp(++i, v) < 0) return;` for an empty while sharing the start of a for (;;) (spring's SorterTemplate.quickSortHelper, found by the semantic check below); it also corrects Vineflower's inverted exit of that shape in ES BulkNeighborQueue$ReservoirTopK.quickSelectIntro, which now recompiles trace-equivalent to the original. - Two-pass case kuna-fixes/pkg/TestJavacTails (javac 17.0.21, 12 methods): default output recompiles to the original bytecode for all 12, compat (Vineflower) differs in 9; tests/kuna_javac_tails.rs pins both modes. - Spec 10.9 paragraph, docs/jvm.md paragraph, case counts (1043 runs). Evidence (javabench full runs kuna-jtails-f3-* vs kuna-fix10-*): methods perfect on every metric 108,652 -> 108,825 of 110,238, bytes exact 108,769 -> 108,942, classes recompiling 7,969 = 7,969; 198 methods better on some metric, 7 worse, all read. 476 methods change text: 405 recompile byte-exact, 56 trace-equivalent, 6 effect-equivalent, 9 read by hand (6 do not compile in either run). Compat output byte-identical on the five Java jars and on okio, okhttp, kotlin-stdlib and kotlinx-coroutines 1.8/1.10 (default output of those too). Corpus 1043/1043, cargo test -p kuna-jvm 681 passed, clippy 1.98.1 clean, check_spec --strict OK. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
jvm_source_locals now also keeps a declaration outside the block where the slot numbers prove the source declared it there, merges back a local Vineflower split when one LocalVariableTable entry names both halves, and keeps x = a; a++; as two statements where the bytecode order shows the source wrote them so. docs/options-jvm.md regenerated for the option's new symptoms and modules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
…ls and while loops by method javac_layout_tails, appended as a CONTEXT recogniser of jvm_javac_layout (tried after the existing method recogniser): the methods around a hunk are javac_layout's same code once a labelled break/continue counts as a bare one, type arguments written after a name are erased (a for header's parts split at their commas), and the varN Vineflower split off for a for header's init is named as the variable the while loop keeps (mapped by its init, or the one loop variable of the new method). Cached per method pair. Unit tests in TestJtails (scripts/tests/test_kuna_fix_diff.py, 135 OK); kuna_fix_check OK. Collateral, base -> new default output of the five Java jars (files differing / unrecognised hunks): guava 23/0, spring-boot 7/0, spring-framework 46/0, elasticsearch 134/2, ghidra 110/15 plus 2 files the tool did not finish in 10 min. Every unrecognised method was read: a for -> while that composes with switch_expression_assignments (ES ScriptMetadata.fromXContent, recompiles trace-equivalent), and tails after loops or while loops where Vineflower's variable merge or split is undone (ghidra TokenPattern, EquateSymbol, FunctionPrototype, PcodeSyntaxTree, PatternBlock, Varnode: now byte-exact; PatchPackedEncode: trace-equivalent). Also: rustfmt of the layout code, module-header notes for the new hooks in merge_helper, inline_single_block_helper and dead_code_helper (no change in output on the five jars; clippy 1.98.1 clean, corpus 1043/1043). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
jvm_javac_layout now also keeps code javac placed after a loop or try there, chooses while or for from the local-variable and line tables, keeps if (a || b) continue; as one if, and keeps early exits inside try-with-resources, finally and synchronized. Both guards against the wrong first-if exit stay (the layout's and loop_first_if_exit's). With the layout's while/for choice, TestDeclarationScopes, TestIncrementStatements and TestLoopFirstIfExit now print the source's while loops and partition's tail after its inner loop; re-recorded. The parent walk over a parameter is the layout's shape, not jvm_source_locals', so kuna_source_scopes checks it in default mode only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
… kotlinc emits Four kuna-only fixes (KunaFix ids; jvm_vineflower_compat on keeps Vineflower's text): - kt_default_args_bridge_lookup: KFunction.parse looked for `<Kotlin name>$default` with a descriptor built from Kotlin types, only in the class. kotlinc names it after the JVM name (@JvmName, internal `$module`, inline-class hash) with the JVM parameters (unboxed inline classes, suspend continuation, erased type parameters) and puts an interface's in DefaultImpls. Defaults printed `= ...` drop from okio 5 / okhttp 27 / kotlin-stdlib 128 / coroutines 1.8 29 / 1.10 25 to 0 / 0 / 0 / 3 / 0, and a `@JvmName("pickInts") fun pick` no longer takes the other pick's default. - kt_default_value_names: default values name parameters by metadata, the extension receiver `this`, a member's instance `this@Class` in an extension (Vineflower's `this.length` read the receiver's length). - kt_default_value_coercion_cast: kotlinc's checkcast to the parameter type is not printed as a cast (`= null as MediaType?`, `= ArrayList() as java.util.List`); kept on lambdas. - kt_delegated_member_default: members generated for `by` delegation (K1 metadata) declare no defaults (coroutines 1.8: 3). kotlinc 2.0 errors (ktc.sh): okio 307 -> 231, okhttp 1939 -> 1482 ("no parameter with name" 75 -> 0, "Expecting member declaration" 157 -> 11), coroutines 1.10.2 12851 -> 12553. New defaults compared with the published sources: okio 6/6, okhttp 35/35, stdlib 242 match (4 data-class copy without source; 4 differ only by existing expression printing defects), coroutines 1.8 35 match + 7 explained. Four two-pass cases (--wrong-output); default texts recompile with kotlinc 2.0.0 to the original bytecode for every method and $default method (constructor init nops aside). Base -> branch default hunks on the five Kotlin inputs: all recognised by the four new recognisers alone; compat trees and the five Java jars (default) byte-identical. Re-recorded: TestDefaultArgsCall (Shape.area(scale: Int = 1)). Gates: cargo test -p kuna-jvm 688+9 passed; corpus 1097/1097, 0 regressions; clippy 1.98.1 clean; check_spec --strict OK; kuna_fix_check OK; test_kuna_fix_diff* OK. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
… scopes and loop layout 109,005 methods perfect on every metric (98.88%), 7,987 classes recompile, 15,363 methods better and 32 worse (27 from the options, 5 from fixes); readTypeAnnotations, filterFields and AbstractClassGenerator.generate no longer score worse than Vineflower. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
…created; companion lookup and refreshed names Three fixes (KunaFix ids, no options): unlisted_anonymous_class (p8_structure/kuna_fix_unlisted_anonymous_class.rs, hook in ClassesProcessor.loadClasses): kotlinc's copies of an inline function's objects and lambdas ($$inlined$ classes) list themselves as anonymous and name the method they were inlined into, which does not list them, so ClassesProcessor left them (and their continuations) at top level: $-named constructor calls and classes reading this$0 / $x$inlined fields. Such a class is now nested in the class its EnclosingMethod names; NestedClassProcessor maps its captures at the new. okhttp 17, kotlin-stdlib 37, coroutines 1.10.2 128 / 1.8.0 125 classes; $$inlined mentions 33/75/404/359 -> 0. kotlinc 2.0 errors: okhttp 1939 -> 1646, coroutines 1.10.2 12851 -> 11537; with the other two fixes 11535 (1.8.0 12525 -> 11180), okio 307 -> 306. Reproducer pkg/TestUnlistedAnonymousClass (kotlinc 2.0.0): default text recompiled unedited (send/offset) and run against the original on 48 inputs: identical output. kt_companion_nameless_class (kotlin_writer.rs): KotlinWriter.writeClass looks the companion up by n.simpleName.equals(..) and throws on an anonymous class sorting before it, failing the whole class (Vineflower 1.12.0 too: KotlinWriter.java:301 NPE); nameless classes are skipped. Exposed in okhttp's Http2Connection by the nesting; unit test on own classes (Vineflower writes no unit for a class it fails whole, so no corpus case). refresh_printed_var_names (p6_variables/kuna_fix_refresh_printed_var_names.rs): VarProcessor.refreshVarNames, run by insertLocalVars on nested classes' methods, renamed a parameter to the clashing name a same-named local already prints: `var countx: Long = countx` (Vineflower jar too; okio Pipe), `valuex = valuex` in 6 coroutines objects once nested. Clashing names are reserved first and only variables without one are renamed. Case pkg/TestRefreshPrintedVarNames (--wrong-output): Vineflower's text is rejected by kotlinc, the default text compiles unedited, runs identically to the original on 30 inputs and its javap -c matches the original's drain except the steps++ temp. Collateral: base vs final default, every hunk accounted (kuna_fix_diff.py: RECOGNISERS unlisted_anonymous_class / refresh_printed_var_names, new UNIT_RECOGNISERS for the units the nesting removes); mine alone accept all but one Rust line number in a failure comment. Five Java jars default byte-identical; compat trees identical apart from kotlin_writer.rs line numbers; corpus 1093/1093, 0 regressions; cargo test -p kuna-jvm green; clippy 1.98.1 clean; check_spec --strict OK. Cases re-recorded (diffs read): TestJavaTypeSyntax, TestInlinedObjectCaptureKt$dropFirst$$inlined$source$1 and TestMembersWithoutMetadataKt$counting$$inlined$feed$1$1 (new compare_fallback: their class is now written inside another unit); the cell case drops TestMembersWithoutMetadataKt.class from its input (Vineflower's text unchanged). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
…empty primary constructors (fixes kt_enum_when_mappings, kt_constructor_accessor_call, kt_empty_primary_constructor; kt_extension_call and kt_legacy_constructor_parameter_checks widened)
- kt_enum_when_mappings: the Kotlin plugin never runs SwitchHelper, so a when over an
enum was written over X$WhenMappings.$EnumSwitchMapping$N[e.ordinal] with numeric
labels. A pass after the main loop reads the mapping from the mapping class's
<clinit> bytecode and writes when (e) { E.A -> .. }, null for a nullable subject's -1,
the default's labels dropped (okhttp 2, kotlin-stdlib 11, coroutines 3 whens).
- kt_constructor_accessor_call: a new of the synthetic <init>(.., DefaultConstructorMarker)
that only forwards (recognised by its code) names the real constructor, marker dropped
(okio 2, okhttp 16, kotlin-stdlib 26 calls).
- kt_empty_primary_constructor: `()` for a primary constructor without parameters when
the class has secondary constructors (okhttp Dispatcher, OkHttpClient.Builder).
- kt_extension_call: a member extension's dispatch receiver may be a local assigned once
from `this` (inlined apply/run/with); okhttp 5, coroutines 1.8.0 7 / 1.10.2 9 calls.
- kt_legacy_constructor_parameter_checks: the opening Kotlin 1.3 checks of a constructor
whose delegation call comes later inside branches are dropped (okhttp MultipartReader);
the call stays in the body.
Evidence: kotlinc 2.0 errors okio 307 -> 305, okhttp 1939 -> 1887, coroutines 1.10.2
12851 -> 12833. Reproducers (kotlinc 2.0.0, five two-pass --wrong-output cases): new
texts recompile unedited (accessor, empty primary, member extension) or with Byte->Int
and the enum getEntries removed (enum); reflection harness, all methods on all enum
values + null and pooled arguments: 33/25/17/73 calls equal to the originals. Five Java
benchmark jars byte-identical by default; compat trees of the five Kotlin inputs
byte-identical; base->new default hunks (96) all accepted by the new recognisers alone.
Gates: cargo test -p kuna-jvm 689+9 passed, 3 ignored; corpus 1099/1099, 0 regressions;
clippy 1.98.1 -D warnings clean; check_spec --strict OK; kuna_fix_check OK; recogniser
tests 200 OK. Re-recorded: TestHiddenEmptySuperCall (Companion(null) -> Companion()).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
… header hunks The four default recognisers pair defaults by parameter name and, when the rest of a header hunk changed too (types, a renamed receiver in the next line), put the changed defaults back and let the tool classify what remains. A context recogniser (FIX_GROUPS ktdefaults_composed) handles hunks the line matching joins because a changed header no longer anchors it (okhttp RequestBody: removed @jvmoverloads overloads before `File.asRequestBody`). Compat-vs-default `other` per changed file (77 of 82 files; 5 exceed the 900 s cap in both runs): the same before and after. test_kuna_fix_diff_ktdefaults 9 OK, test_kuna_fix_diff 145 OK, kuna_fix_check OK. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
…hanges on the same lines kt_enum_when_mappings in context: a when hunk whose subject or branch code another fix also changed (compat `CancellableKt.startCoroutineCancellable(block, ..)` against `block.startCoroutineCancellable(..)`) is accepted when each such line pair is another recogniser's. Verified on kotlinx-coroutines 1.8.0/1.10.2 CoroutineStart.kt compat vs default: 0 other (base 1 and 0). Recogniser tests 201 OK, kuna_fix_check OK. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
Fixes kt_default_args_bridge_lookup (Kotlin defaults read from the $default method kotlinc emits, found by its JVM name and descriptor, also in DefaultImpls; Vineflower looked it up by the Kotlin name and could print another function's defaults), kt_default_value_names, kt_default_value_coercion_cast and kt_delegated_member_default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
…unk another fix changed too With the anonymous-class constructor calls and the object expressions or lambdas written for them one placeholder, and without the locals kept for an object that nothing else reads, the rest of the hunk is unchanged or another fix's recogniser accepts it (compat-vs-default in kotlinx-coroutines: BroadcastKt, FlowKt__ZipKt and FlowKt__MigrationKt, where an extension or facade call is resugared around the object). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
…ls (fixes ambiguous_type_variable_argument, unboxed_argument_type) ambiguous_type_variable_argument: appendParamList casts an ambiguous argument to the parameter's type with the method's type variables replaced by the inferred type. For a parameter declared as the method's own type variable that is the argument's own type, which makes a more specific non-generic overload applicable: Guava Joiner/MapJoiner appendTo(StringBuilder, Iterator) printed this.appendTo((StringBuilder)builder, parts), a call of itself. Where the call as written resolves elsewhere (JLS 15.12.2 phases 1-2 over the named class's member methods), the argument is cast to the variable's erasure and inference binds the variable to it, so a result cast the bytecode makes is printed: appendTo((Appendable)builder, parts). No upstream fix on develop/1.13.0. unboxed_argument_type: the generic signature's parameter type replaced the primitive type appendParamList chose for an argument it writes without its boxing call, so a byte/short constant lost or swapped its narrowing cast: spring-framework BeanUtils Map.of(.., byte.class, 0, short.class, 0, ..) boxed Integers. The chosen type now stays: (byte)0, (short)0. Evidence: reproducers TestAmbiguousTypeVariableArgument and TestUnboxedArgumentType (javac 17.0.21.0.3 -g); default output recompiles to bytecode identical to the original for every class (javap -c -p); compat text recompiled throws StackOverflowError in appendTo/appendAll, calls show(String), boxes Byte/Short/Integer for Short/Byte/Byte. Collateral (default before vs after, 5 Java jars, okio/okhttp/kotlin-stdlib/coroutines 1.8.0+1.10.2 as Kotlin and as Java): 3 hunks, Joiner 2 and BeanUtils 1, all recognised; Kotlin output byte-identical. Compat output byte-identical on the 5 Java jars. javabench kuna-jwrong5 vs kuna-fix12: 2 methods better (Joiner appendTo, now byte-exact), 0 worse; classes recompiling 7987 -> 7988. Gates: cargo test -p kuna-jvm 687 passed, 0 failed, 3 ignored; corpus 1095/1095, 0 regressions; clippy 1.98.1 -D warnings clean; check_spec --strict OK; kuna_fix_check OK; test_kuna_fix_diff 149 OK. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
Fixes ambiguous_type_variable_argument (an argument cast to its own type for a type-variable parameter made javac call another overload: Guava's Joiner.appendTo called itself) and unboxed_argument_type (byte and short constants passed to a generic parameter boxed as Integer). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
Fixes kt_enum_when_mappings (Kotlin when over an enum printed through its synthetic $WhenMappings array), kt_constructor_accessor_call (synthetic DefaultConstructorMarker forwarders called with a null marker) and kt_empty_primary_constructor, and widens kt_extension_call and kt_legacy_constructor_parameter_checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
…sted copy is given
Three nested copies lost a constructor argument: okhttp's
sendDegradedPingLater and pushRequestLater objects and kotlin-stdlib's
DeepRecursiveScopeImpl continuation (EmptyCoroutineContext) kept an
init { this.$x = $x } store of a value they did not show. kotlinc gives no
debug name to the locals holding an inlined function's arguments there;
StackVarsProcessor.setEffectivelyFinalVars keeps a local an anonymous
class's constructor takes only through its LocalVariableTable entry, so the
Kotlin inliner folded the local into the new and NestedClassProcessor saw no
capture. For a class unlisted_anonymous_class nested, a local whose
definition a constructor argument reads (directly, through SSA phi roots or
a stack copy) now stays a local (takes_local).
A captured variable took its name from the creating method variable's debug
entry, while the creating method declares the variable from its first
store, which has none when kotlinc re-stores an inlined argument under the
debug name (arrayOf(..) as flows$iv in coroutines' combine, okhttp's
pingIntervalNanos): the object read a name nothing declares. The entry is
renamed to the creating method's name (captured_lvt).
Kept as Vineflower's top-level classes: suspend lambdas in Kotlin output
(the writer prints a failure comment at the site, which shows no argument)
and copies created in synthetic methods remove-synthetic hides.
Old vs new constructor-call arguments of every nested copy (base vs final
default trees): okhttp 17 calls / 97 arguments, kotlin-stdlib 37 / 18,
coroutines 1.10.2 62 / 54, 1.8.0 62 / 52: no value argument missing; the 25
variable arguments the object does not mention (okhttp 20, coroutines 5 per
version) are fields the old class never reads outside its constructor.
Every argument whose field the old class reads reaches the object (okhttp
51, stdlib 18, co110 49, co18 44): written there or declared as a local the
object reads. $$inlined mentions okhttp 33 -> 0, stdlib 75 -> 0, coroutines
404 -> 21, 359 -> 21 (kept suspend lambdas and synthetic-method copies).
kotlinc 2.0 errors: okhttp 1939 -> 1637, coroutines 1.10.2 12851 -> 11786,
1.8.0 12525 -> 11428, okio 307 -> 306.
Unit tests on own kotlinc 2.0.0 classes (tests/data/ktinlined2): a nameless
captured local stays a local; a suspend-lambda copy and a synthetic-method
copy stay classes; a re-stored argument is read by the name its creator
declares (that text recompiles and returns what the original returns on
1920 inputs; the previous text failed: unresolved reference 'values$iv').
Five Java jars byte-identical to base; compat trees of the Kotlin inputs
identical to base apart from kotlin_writer.rs line numbers in failure
comments.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
Fixes unlisted_anonymous_class (the anonymous classes kotlinc copies out of inline functions are nested where their EnclosingMethod says, so they print as object expressions with every value they are given), kt_companion_nameless_class and refresh_printed_var_names (a renamed parameter took a local copy's name, printing `var countx = countx`). Also carries the branch's edits to three existing kuna-fixes cases (compare_fallback) and restores four case summaries that earlier merges of ktunrep, jpatterns, ktanon and ktctors dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
…rfect on every metric Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
…l, kt_raw_generic_type Kotlin output only; compat mode byte-identical to the base on okio 2.6.0, okhttp 4.7.2, kotlin-stdlib 2.2.20 and kotlinx-coroutines 1.8.0/1.10.2; the five Java benchmark jars are byte-identical to the base in default mode. - kt_implicit_widening: a Byte, Short or Char value (or an Int behind an implicit i2l) stored, passed or returned where a wider numeric type is expected takes the conversion the source wrote (`this[i++].toInt()`, `c.code`, `readShortLe().toLong()`); Vineflower wrote it alone. - kt_object_monitor_call: wait/notify/notifyAll are called on `(x as java.lang.Object)`, as the source writes them; Kotlin's Any has none of them. - kt_raw_generic_type: kotlinc writes no LocalVariableTypeTable and Kotlin has no raw types. Locals of generic classes are declared with the arguments the class file proves (stored values' generic types, then parameter/return sinks and stored elements), mutable only where the method mutates through them, `ObjectRef<Any?>`, star projections where nothing proves them and nothing writes through; casts take their target's or operand's arguments, type tests star projections, both by Kotlin's read-only names; casts the typed locals make redundant are left out. kotlinc 2.0 errors over the whole tree (default mode): okio 307 -> 197, okhttp 1939 -> 1343, kotlinx-coroutines 1.10.2 12851 -> 11590. Cases TestKtImplicitWidening, TestKtObjectMonitorCall, TestKtRawGenericType (kotlinc 2.0.0, two-pass); their default texts recompile with kotlinc 2.0.0 and 13 of 18 methods are javap-identical to the originals (the rest differ only in statement shapes Vineflower writes without these fixes; one lambda method with a captured var is left out of the recompile). Every base->branch hunk on the five Kotlin inputs is accepted by these fixes' recognisers alone. Corpus 1095/1095 (14 kuna-fixes default texts re-recorded with this change). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
…n fixes' hunks kt_raw_generic_type's recogniser writes its types back raw and asks the other Kotlin fixes' recognisers (and, for a hunk inside a local another fix keeps, their context recognisers); the java.lang/java.util prefix is dropped only from the collection classes it renames. On okio's and okhttp's 71 changed files the compat->default 'other' hunks are those of the base (13 = 13 on the 42 files whose runs end within 300 s). Recogniser tests: 197 OK; kuna_fix_check OK. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
Fixes kt_raw_generic_type (Kotlin locals, casts and tests of generic classes get the type arguments the class file proves, star projections where nothing does), kt_implicit_widening (the .toInt()/.code/.toLong() Kotlin needs where Java widens implicitly) and kt_object_monitor_call (wait/notify/notifyAll through java.lang.Object). Re-recorded TestKtEnumWhenMappings (a Byte temp returned as Int now .toInt()) and TestUnlistedAnonymousClass (UnlistedSource<Int>, UnlistedSink<*>) for the combination with this branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
…yped_array, kt_narrow_constant_local Three kuna-only fixes of Kotlin output (no options; off under jvm_vineflower_compat): - kt_legacy_parameter_checks: ResugarKotlinMethodsPass drops Kotlin 1.4's Intrinsics.checkNotNullParameter but keeps Kotlin 1.3's checkParameterIsNotNull; the checks that open a function's first block (each of a parameter written non-null) are dropped. okio 2.6.0 449 calls and okhttp 4.7.2 764 to 0; kotlinc 2.0 "illegal usage of inline parameter" okio 11 / okhttp 10 to 0. - kt_inlined_to_typed_array: kotlinc's inlined toTypedArray() (Collection.toArray(new T[0]), Kotlin 1.3's with its TypeCastException null check) is written c.toTypedArray() (toTypedArray<T>() where the receiver's element type is not T); the null check is removed and the casts of the result left out. okhttp 18, okio 1, kotlin-stdlib 6, coroutines 1.8.0/1.10.2 5+5 sites to 0. - kt_narrow_constant_local: a local of integer constants (or if-expressions of them) that Vineflower types Byte/Short by its values is typed Int when every read takes an Int. okio 12, okhttp 6, kotlin-stdlib 5, coroutines 3+3 locals. Evidence: reproducers TestKtLegacyFunctionChecks, TestKtLegacyToTypedArray (kotlinc 2.0 then rewritten to Kotlin 1.3's shapes with ASM), TestKtNarrowConstantTemps recompile with kotlinc 2.0 (edits only for pre-existing defects); javap -c -p per method: 10/13, 12/13 identical (rest: Vineflower's own forms, checks identical), and every method of the three classes run on sample inputs against the original: 151 + 27 + 56 calls, 0 differences. kotlinc 2.0 errors okio 117 -> 105, okhttp 529 -> 500, coroutines 1.10.2 10178 -> 10173. Base vs new default trees: every hunk recognised by the new kuna_fix_diff recognisers (okio, okhttp, kotlin-stdlib, coroutines 1.8.0 and 1.10.2); compat trees byte-identical; the five Java benchmark jars byte-identical by default. Six kuna-fixes cases re-recorded after review (their Kotlin 1.3 checks dropped, Byte temporaries typed Int). cargo test -p kuna-jvm 719 passed, 3 ignored; corpus 1129/1129, 0 regressions; clippy 1.98.1 -D warnings clean; check_spec --strict OK. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
…with other fixes' hunks kuna_fix_diff.py: CONTEXT_RECOGNISERS['kt_legacy_parameter_checks'] takes the Kotlin 1.3 checks that open functions out of the old file and accepts a hunk holding such a check when every overlapping hunk of the result is recognised (compat vs default, where one hunk holds a dropped check and another fix's change). test_kuna_fix_diff_ktlegacy: 5 tests OK; all test_kuna_fix_diff* modules 233 OK; kuna_fix_check OK. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
…ructor, kt_data_class_property_parameters, kt_function_parameter_name, kt_contract_import, kt_unlabeled_sequence_break Six Kotlin-output declarations and names kotlinc 2.0 rejects (KunaFix ids, no options): - kt_escaped_class_name: a class whose Kotlin name is no identifier is written in backticks in short names, imports and outer-instance constructor calls (okio `public val Okio: `-DeprecatedOkio` = `-DeprecatedOkio``). - kt_inner_class_constructor: an inner member class constructed on the current instance is called by its simple name (okhttp `RealCacheRequest(editor)`). - kt_data_class_property_parameters: a data class's primary constructor declares the properties whose fields it stores first (stores taken out of init), and the SYNTHESIZED members (componentN, copy, equals, hashCode, toString) are hidden. - kt_function_parameter_name: a function/constructor body reads a parameter without a LocalVariableTable entry by the header's metadata name, not var1. - kt_contract_import: `contract` imported (or qualified) and the function annotated @OptIn(ExperimentalContracts::class). - kt_unlabeled_sequence_break: an unlabelled break whose closure is a sequence is written `break` (Java's text), not `return@run` with no run around it. Evidence (verified): - kotlinc 2.0 over the whole default trees: okio 117 -> 108 errors (28 -> 29 files clean), okhttp 529 -> 470 (55 -> 60), kotlinx-coroutines 1.10.2 10,178 -> 10,080 (104 -> 107). - Six two-pass --wrong-output Kotlin cases (kotlinc 2.0.0 -jvm-target 17): every compat text rejected by kotlinc 2.0 (27/11/26/11/9/7 errors), every default text (decompiled with the JDK runtime) compiles; javap -c -p of the recompiled classes equals the original for all methods but init-block nops, VF's own `twice`/`shift` inlining forms and the sequence-break `write`, which ran 200,000 random inputs against the original with 0 differences. - Collateral: every base->branch default hunk on okio, okhttp, kotlin-stdlib, kotlinx-coroutines 1.8.0 and 1.10.2 is accepted by these six recognisers alone; compat trees of the five Kotlin inputs byte-identical to base; the five Java benchmark jars byte-identical in default mode. - cargo test -p kuna-jvm 714+9 passed, 3 ignored; corpus 1135/1135, 0 regressions; clippy 1.98.1 -D warnings clean; check_spec --strict OK; kuna_fix_check OK; test_kuna_fix_diff* 234 OK. Re-recorded (diffs read: an inner class constructed by its simple name): TestDefaultArgsCtorDescriptor, TestExtensionReceiver, TestSupertypeTypeArguments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
…er Kotlin fixes' hunks compat->default on the changed files of okio, okhttp, kotlin-stdlib, kotlinx-coroutines 1.8.0 and 1.10.2 (kuna_fix_diff.py per file, 300 s cap) showed six hunks where kt_data_class_property_parameters met another fix's change in the same lines (Pair, Triple, IndexedValue, TimedValue: generic types and Java casts; okhttp Header and CustomTrustRootIndex: legacy parameter checks). The context recogniser now drops the lines the fix takes out of the old side and takes the new header back to plain parameters, then requires the rest of the hunk to be accounted for as before (one level deep). Verified: those files' `other` hunks are now the base's own (AndroidPlatform:168); every other changed file's `other` set equals the base's (AwaitAll checked with a 1800 s cap); every base->branch hunk is still accepted by the six recognisers alone; kuna_fix_check OK, test_kuna_fix_diff* 234 OK. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
Fixes kt_escaped_class_name, kt_inner_class_constructor, kt_data_class_property_parameters, kt_function_parameter_name, kt_contract_import and kt_unlabeled_sequence_break: declarations and names kotlinc rejected (class names with `-`, inner classes built through the outer class's name, data class properties declared in the body with their generated members printed, contracts without their import, a return@run with no run). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
Fixes kt_legacy_parameter_checks (kotlinc 1.3's checkParameterIsNotNull at the start of every Kotlin function, which kotlinc rejects on inline lambda parameters), kt_inlined_to_typed_array (1.3's inlined toTypedArray() printed as toArray(arrayOfNulls(0))) and kt_narrow_constant_local (constant temps typed Byte or Short where every use takes an Int). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
…ine state machine Four Kotlin-output fixes (KunaFix ids, no options): - kt_suspend_state_machine: a CFG pass first in the Kotlin graph passes recognises a metadata-suspend function's state machine instruction by instruction (entry, label dispatch, spill/label stores, the suspend call passing the continuation, the COROUTINE_SUSPENDED check, resume blocks of restores + throwOnFailure(result)) and rewrites the graph into the direct path; split try ranges are joined; a Unit tail call's check is removed. Shapes it cannot map (continuation used by inlined suspendCoroutineUninterceptedOrReturn, aliases in reused slots) keep the machine. - kt_suspend_call_continuation: suspend calls in a suspend function are written without the $completion argument; $completion.getContext() is kotlin.coroutines.coroutineContext; the cast/unboxing kotlinc puts on a suspend call's Object result is left out where it is the callee's declared type; such results are as nullable as declared. - kt_coroutine_boxing: Boxing.boxInt(x) etc. written as implicit boxing. - kt_suspend_function_type: metadata suspend function types are written suspend (T) -> R. Evidence (verified in this run): - kotlinx-coroutines 1.10.2: 79 of 87 metadata-suspend state machines and 52 tail-call checks rebuilt; kotlinc 2.0 errors 10,178 -> 6,979 (clean files 104 -> 107). 1.8.0: 106 of 114 machines, 52 checks; 9,792 -> 5,597. okio and okhttp output byte-identical (117 / 529 errors unchanged). - Reproducers (kotlinc 2.0.0) recompiled from the decompiled text: 540/540 scripted runs (suspending and immediate calls, resumption with values and exceptions) equal to the original classes; 14/21 methods byte-identical after normalisation, the rest differ in local slots, loop forms and an inline call that is inlined. Five rebuilt kotlinx-coroutines 1.8.0 functions (joinAll x2, cancelAndJoin, fixedDelayTicker) recompiled unchanged and behave as the library on 5 scenarios; cancelAndJoin and fixedDelayTicker recompile to identical bytecode. - Collateral: compat trees byte-identical to the base on okio, okhttp, kotlin-stdlib and both kotlinx-coroutines; base -> new default diffs on kotlin-stdlib, co110, co18: 0 other hunks in kuna_fix_diff.py with the new recognisers; the five Java benchmark jars byte-identical. - Gates: cargo test -p kuna-jvm 714 + 9 passed, 3 ignored; corpus 1133/1133, 0 regressions; check_spec --strict OK; clippy 1.98.1 clean; kuna-cli jvm_cli 43 passed; clitests 330/331 (the known datatest probe); test_kuna_fix_diff 155 OK. Five new two-pass cases; seven existing kuna-fixes default texts re-recorded (their suspend functions are now direct bodies) and the unit tests of kt_synthetic_object_super_call, captured_var_through_cast, handler_copy_shared_body and kt_anonymous_class_failure_reason run with kt_suspend_state_machine off; the unlisted_anonymous_class test reads the tail call without its check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
Fixes kt_suspend_state_machine (a suspend function is printed as its direct body instead of kotlinc's state machine: continuation class, label dispatch, spills and COROUTINE_SUSPENDED checks), and kt_suspend_call_continuation, kt_coroutine_boxing and kt_suspend_function_type for the calls, boxing and function types around it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
kuna could only decompile native binaries. This adds a second engine for the JVM: point the same commands at a
.classfile, a jar or a class directory and get Java back, or Kotlin for Kotlin classes.Before:
After:
What it is
decompiler/nonnative/kuna-jvmis a Rust port of Vineflower 1.12.0 (Apache-2.0, credited inNOTICE) with its Kotlin, IDEA-not-null and variable-renaming plugins. It keeps Vineflower's Java-level IR and does not lower bytecode to p-code: class files carry types, exception tables and structure that p-code would discard and the native passes would have to guess back. It shares the CLI, the option catalog, the output shapes and the phase-folder layout with the native engine, and no code withkuna-decomp.--option jvm_vineflower_compat onthe output is byte-identical to Vineflower's for the same input, options and JDK, which makes Vineflower's own test suite the regression oracle. By default kuna also fixes Vineflower bugs (below).decompile,decompile-all,decompile-project,functions,catalog --engine jvm,test --jvmanddocs jvmaccept JVM inputs. The engine is picked from the file's magic; flags that only make sense for native images exit 2. The 74 engine options arejvm_*rows in the same catalog (docs/options-jvm.md)./usr/bin/gzipis byte-identical, each invocation costs about 1 ms more, and the binary grows from 38.4 to 50.1 MB.Compared with Vineflower
All 110,238 methods of five projects, scored against the original source on control flow, types and recompiled bytecode. In compat mode every score is identical to Vineflower's; the times below are from that mode, both tools back to back on one machine, Vineflower with 12 threads.
docs/jvm-benchmark.mdhas the commands to rerun it (scripts/javabench).On a single thread kuna is still 1.2 to 2.2 times slower than Vineflower.
Where kuna departs from Vineflower
kuna fixes 238 Vineflower 1.12.0 bugs in its own engine, 131 of them in Kotlin output. They are always on and have no options;
--option jvm_vineflower_compat onturns them all off. Each has a case intests/jvm/kuna-fixes/checked in both modes, anddocs/jvm.mdlists them.try, pattern switches, Kotlin references, default-argument constructors and suspend functions, afinallykotlinc split around a suspension point, exception ranges kotlinc 1.3 made overlap without nesting, ...(long) a * bprinted as anintproduct that overflows, a cast that makes javac call the method itself (Guava'sJoiner.appendTo), afinallyrun once per loop iteration, unboxing dropped so that boxes are compared by identity, raw generic constructors,assertas an expression, the casts and type arguments generic inference needs,Arrays.asList(new T[n])printed asArrays.asList(),build(new String[0])printed as a call to itself, casts dropped so that another overload is called, doubles printed as float literals,Objects.requireNonNull(x);deleted,o.super(..)printed assuper(..), a write in afinallylost to the code after thetry, asynchronizedbody run outside its lock or under a re-read lock, hand-written code turned into try-with-resources, ... In Kotlin: suspend functions printed as kotlinc's state machine (continuation class,labeldispatch, spilled locals) rather than their body, raw generic types, which Kotlin does not have, data classes whose properties are declared in the body, inner classes constructed through the outer class's name, kotlinc 1.3's parameter checks on inline lambda parameters,ByteandCharvalues used asIntwithout the conversion Kotlin requires, default values printed as= ...or as another function's,whenover an enum through kotlinc's$WhenMappingsarrays, the objects kotlinc copies out of inline functions printed as separate$$inlinedclasses,@Volatileand@Transientdropped from properties, enum constants that lost their arguments, extension properties declared without their receiver, accessors that call themselves instead of readingfield,$in strings read as a template, property initializers and companion constants lost,@Synchronizedaccessors that lost the flag,!!dropped, nullable values cast as non-null, an extension property assignment that assigned the receiver, calls through facade classes Kotlin cannot name, operators parenthesised by Java's precedence (x or 1 shl kmeans(x or 1) shl k),!=for identity tests,CharandBytearithmetic typed as in Java, line breaks that end a statement early, parameters namednewordefaultread through undeclared variables, field reads and calls inlined past code that changes their result,$names and keywords without backticks, droppedreturns, wrongforloop bounds,$defaultbridge calls, constructors printed as functions, abstract functions with bodies, Java member names (.length(),charAt), classes without Kotlin metadata written in Java syntax, ...x = e + xasx += eeither),new Inner()instead ofOuter.this.new Inner(), switch cases in source order without javac's table fillers and synthetic default, and a test and cast where javac's bytecode shows no pattern. 2 make failure comments name their cause.Three options, on by default and off under
jvm_vineflower_compat on, write equivalent code in the form the class file shows the source had:jvm_javac_layoutlays out ifs, loops and exits as javac laid out the bytecode (two returns stay two returns rather thanreturn c ? a : b, ifs keep the source's polarity, earlyreturn;,continue;andbreak;stay, also inside loops andtry, code javac placed after a loop stays after it,whileandforas the source wrote them,false == xstays), for classes without Kotlin metadata;jvm_source_localskeeps the locals the class file names instead of folding them into their use, declares them in slot order and in the source's scopes, and keeps++and--statements the source wrote as statements;jvm_source_patternskeeps a test and a cast the line table shows the source wrote, rather than aninstanceofpattern.On the benchmark, default mode raises the methods perfect on every metric from 94,106 to 109,007 (85.37% to 98.88%; every project above 98.7%), recovers all 17 methods Vineflower fails or prints unparseable, and makes 380 more classes recompile (7,988 of 8,155), losing none. 15,365 methods score better; 32 score worse on at least one metric (
docs/jvm-benchmark.mdlists them with the cause). On Kotlin libraries the methods left as failure comments drop from 38 to 0 in kotlin-stdlib, from 2 to 0 in okio, from 3 to 0 in okhttp and from 71 to 0 in kotlinx-coroutines (decompiled as Java withjvm_plugins 0, no method fails either, where Vineflower fails 24), and calls through$defaultbridges from 446 to 20 over okio, okhttp, kotlin-stdlib and kotlinx-coroutines. Compiled whole with kotlinc 2.0, the decompiled okio has 96 errors instead of 6,185, okhttp 441 instead of 9,988 and kotlinx-coroutines 6,876 instead of 18,722 (31 of 50, 63 of 131 and 110 of 396 files compile, instead of 2 of 50, 3 of 148 and 43 of 506).Robustness: recursion too deep for the stack fails that one method or class with a
StackOverflowErrorinstead of ending the run, and the per-method time limit counts work rather than wall time, so the same methods stop on every run at any worker count.Tests
make test-jvmruns 1,151 cases: 607 byte-identical to Vineflower's output in compat mode (530 Java cases from Vineflower's suite, 6 for the two small plugins, 71 Kotlin), and the 272 fix and option cases in both modes. No JDK is needed, anddecompiler/nonnative/kuna-jvm/tools/oracleregenerates the expectations from Vineflower.tests/cliprobes for the new surface.Not covered yet on JVM inputs:
strings,xrefs,disassembleand the call-graph options.🤖 Generated with Claude Code
https://claude.ai/code/session_013BBaWRszChd3Ho4R3P4cA4