Skip to content

Releases: ejfkdev/jcdc

v0.1.3

Choose a tag to compare

@github-actions github-actions released this 25 Oct 06:45
v0.1.3
6cc5f0f

更新内容

正确性(本版重点)

  • 条件表达式不再重复求值已存储的调用。 x = f(); if (x == null) 的字节码是 dup; astore; ifnull;旧实现把调用表达式留在操作数栈上,条件里又打印一次 f()——while (reader.readLine() != null) 与循环体各读一次,MailcapFile 每轮消耗 3 行、AppleScriptEngine 2 行。现在分支读取刚写入的局部变量,且仅限紧随的条件分支(允许中间隔常量比较,如 dup; istore; iconst_m1; if_icmpeq),不存在「局部变量已被重新赋值」的风险。
  • 布尔类型推断收紧。 裸 v == 0 不再单独作为 boolean 的证据:javac 对 int 与 boolean 使用同一条测试指令,int p = ports[i]; if (p == 0) 以前会被声明成 boolean p(boolean p = ports[i] 根本无法编译)。现在要求 0/1 值域证据,并让明确的 int 生产点(调用结果、数组元素、int 字段/强转、算术)否决 boolean;v = v & flag、拷贝等歧义形态既不证明也不否决。
  • 量化:把 rt.jar 中受影响的类重新交给 javac(--release 8,rt.jar 作 classpath),327 个文件的编译错误减少、净减 2,695 条;仅 1 个原本已损坏的文件增加 2 条;没有新增语法错误文件(jclassd 全量语法门禁仍是基线的 1 个——ASM 类名含 (),任何 Java 源码都无法拼写)。

Java 27 / 28 支持

  • 解析 class 文件版本 71/72(Java 27/28),语料补入 Temurin 27+35 与 28-beta+18;完整 JDK 27/28 镜像反编译 0 失败,features 套件在两版各 10/10。

其他缺陷修复

  • record 规范构造器不再输出非法的 super();。
  • 被调用、却被 --synthetic 隐藏的 helper 不再删除(JetBrains 的 $$$reportNull$$$0)。
  • throw 的合并值改用池感知的 LUB(此前退化成 Object)。
  • 数组 checkcast 按描述符解析类型(此前 76 个单元把 [Ljava/lang/Class; 当成类名)。

性能

  • CLI 改用 mimalloc 全局分配器;内部迁移到 jdc-core 0.2.17(Arc<str> IR、Fx 哈希)。
  • 嵌套类反向引用索引改为单飞构建:此前每个渲染 worker 各建一次、全部争用类池互斥锁(JDK 28 镜像系统态时间 19.3s→11.1s)。
  • 18 核 Apple M5 Pro:rt.jar 并行 2.3s / 686MB(单线程 9.4s / 427MB);JDK 28 完整镜像(15,039 个单元)4.7s / 978MB(单线程 23.3s / 525MB)。
  • 对比 Vineflower / CFR / Procyon / fernflower:rt.jar 上约 11× / 23× / 48× / 93×,JDK 28 镜像上约 31× / 106× / 154×(Vineflower 无法完成该负载)。
  • 输出确定性保持不变:并行与 JCDC_THREADS=1 逐字节相同。

验证:features 59/59 × 两条结构化路径;语料 spot 与上一版逐方法字节一致;rt.jar 12,609 个单元全量、无 panic;jclassd 全量语法门禁 1 个(既有基线)。

What's changed (English)

Correctness (the headline of this release)

  • Conditions no longer re-evaluate a call the store already performed. x = f(); if (x == null) compiles to dup; astore; ifnull; the old builder left the CALL on the operand stack, so the branch printed f() a second time — while (reader.readLine() != null) above a body reading the same stream again (MailcapFile consumed three lines per iteration, AppleScriptEngine two). The store now rewrites the surviving duplicate to a read of the local it just wrote, restricted to the immediately-following conditional branch (constants in between are fine, e.g. dup; istore; iconst_m1; if_icmpeq), so a reassignment can never make the local stale.
  • Boolean inference tightened. A bare v == 0 is not boolean evidence on its own: javac has no distinct test opcode, so int p = ports[i]; if (p == 0) used to be declared boolean p — which does not even compile (boolean p = ports[i]). A boolean inference now also requires 0/1 value-range evidence, and an unambiguous int producer (call result, array element, int field/cast, arithmetic) vetoes it; ambiguous shapes (v = v & flag, copies) neither prove nor veto.
  • Measured by recompiling the affected rt.jar classes with javac (--release 8, rt.jar as classpath): 327 files with fewer errors, −2,695 total; one already-broken file gains 2; no new syntax-error file (the jclassd corpus gate stays at its baseline of 1 — ASM's class name containing (), unspeakable in Java source).

Java 27 / 28 support

  • Class-file versions 71/72 are parsed; Temurin 27+35 and 28-beta+18 are in the corpus. Full JDK 27/28 images decompile with 0 failures; the features suite is 10/10 on each.

Other fixes

  • Record canonical constructors no longer emit an illegal super();.
  • Called helpers hidden by --synthetic are no longer dropped (JetBrains $$$reportNull$$$0).
  • Merged throw values take a pool-aware LUB instead of degrading to Object.
  • Array checkcasts resolve their type from the descriptor (76 units previously treated [Ljava/lang/Class; as a class name).

Performance

  • mimalloc as the global allocator; internal migration to jdc-core 0.2.17 (Arc<str> IR, Fx hashing).
  • The nested-class reverse-reference index is built once per pool instead of once per render worker (JDK 28 image: kernel-side time 19.3 s → 11.1 s).
  • Apple M5 Pro, 18 cores: rt.jar 2.3 s / 686 MB parallel (9.4 s / 427 MB single-threaded); full JDK 28 image (15,039 units) 4.7 s / 978 MB (23.3 s / 525 MB single-threaded).
  • Against Vineflower / CFR / Procyon / fernflower: ~11× / 23× / 48× / 93× on rt.jar, and ~31× / 106× / 154× on the JDK 28 image (Vineflower cannot finish it).
  • Deterministic output is unchanged: parallel and JCDC_THREADS=1 runs are byte-identical.

Verification: features 59/59 on both structurer paths; corpus spot byte-identical per method to the previous release; rt.jar 12,609 units, panic-free; jclassd syntax gate at its baseline of 1.

Full Changelog: v0.1.2...v0.1.3

v0.1.2

Choose a tag to compare

@github-actions github-actions released this 15 Sep 07:19
v0.1.2
f3c45cb

更新内容

语料规模语法校验:1,671 → 1 个语法错误文件。 用 48,251 个第三方 .class(CFR 各 javac 版本回归套件、fernflower/ASM 测试数据、完整 JDK 26 镜像含源码、java-class-rs 边角用例)做「反编译 → javac 回编译」全量校验,逐类修复解析级错误:

  • 目录输入的类池索引:原先按「输入相对路径」而非 class 自身内部名索引,凡目录树不是以包根为根(如 cfr/java_8/…、jdk26/jdk26/class/<module>/…)就导致按内部名查找全部落空——匿名类无法内联、输出非法的 Outer.1。现按 this_class 索引(不可解析的文件保留路径键),重复名的解析顺序也确定化了。这一类占原错误的约 1,300 个文件。
  • 被丢弃的值合法化:a.a;、Outer.this;、(Object[]) foo.clone(); 都不是合法语句表达式——强转剥到有副作用的核心,可能抛 NPE 的读取物化进合成局部变量(保留原语义)。
  • $ 名字渲染:字面 $ 名(DolTest2$$dollah$$)与 javac 局部类编码(Outer$1Name)在点号化后无法解析时一律保留 $。
  • 数组下标加括号:(new int[1][0])[0] = …(裸写会被重解析成三维数组创建)。
  • try-with-resources 残留形态:资源被改写为赋值时先输出赋值再普通 try;无资源且无 catch/finally 时退化为语句块(裸 try { 无法编译)。
  • Java 21+ 模式 switch 还原:enumSwitch 常量标签(含 EnumDesc 条件常量解码)、typeSwitch 整数标签——修复 JDK 26 PackageBuilder/DocLint 一类的方法。
  • 匿名类作为构造参数:内联后继续下钻参数,new Outer(new Inner(null) {…}) {…} 正确还原;常量监视器(synchronized (null))物化为局部变量。

唯一剩余文件是 ASM 的 Artificial$()$Structures——类名含 (),任何 Java 源码都无法拼写(CFR 输出同样非法),属不可还原输入。

验证:features 语义套件 56/56;rt.jar 全量 12,609 文件零 panic;语料保真度 A/B 与改动前完全一致(fail_files 集合、6/6 族可重编译)。

性能基准更新:加入 JetBrains fernflower 对照;第二个负载换成完整 JDK 26 模块镜像(JDK 9+ 已无 rt.jar,boot classpath 变为 lib/modules)。18 核 M5 Pro:rt.jar 6.8s / 599MB(fernflower 196.8s,Vineflower 26.2s,CFR 59.4s,Procyon 113.1s);JDK 26 镜像 11.9s / 801MB(CFR 175.8s,fernflower 513.1s,Procyon 789.4s;Vineflower 跑不完该负载——8g/16g 堆耗尽,32g 封顶 40 分钟仍 0 输出)。

新增 scripts/jclassd_syntax_check.py(语料语法校验脚本)与 “Built with ZCode” 徽章。

What's changed (English)

Corpus-scale syntax sweep: 1,671 → 1 files with parse errors, measured by decompiling a 48,251-class third-party corpus and feeding every emitted source back through javac:

  • Pool indexing for directory inputs now keys classes by their own this_class instead of the input-relative path — any tree that is not a package tree rooted at the input dir made every internal-name lookup miss, killing family/anonymous enumeration and emitting illegal Outer.1 references (~1,300 files).
  • Discarded-value legalization: a.a;, Outer.this;, (Object[]) foo.clone(); are not statement expressions — casts strip to their side-effecting core, trapping reads materialize into a synthetic local (NPE semantics preserved).
  • $-name rendering keeps $ when dotting cannot parse (literal-$ names, javac's Outer$1Name local-class encoding).
  • Array indexing parenthesizes its base: (new int[1][0])[0] = ….
  • Vestigial try-with-resources: hoisted resources print before a plain try; a resource-less try with no catch/finally becomes a block.
  • Java 21+ pattern switches restored, including enumSwitch constant labels and EnumDesc conditional-constant decoding (fixes JDK 26 PackageBuilder/DocLint-style methods).
  • Anonymous classes as constructor arguments are now inlined; constant monitors (synchronized (null)) materialize into a local.

The one remaining file is ASM's Artificial$()$Structures — a class name containing () that no Java source can spell (CFR emits the identical invalid form).

Verification: features battery 56/56; rt.jar smoke 12,609 files, no panics; corpus fidelity A/B byte-identical to the pre-change baseline. Benchmarks updated with JetBrains fernflower and a second workload: the full JDK 26 module image (JDK 9+ has no rt.jar) — jcdc 11.9 s / 801 MB vs CFR 175.8 s, fernflower 513.1 s, Procyon 789.4 s; Vineflower cannot finish it.

Full Changelog: v0.1.1...v0.1.2

v0.1.1

Choose a tag to compare

@github-actions github-actions released this 14 Sep 12:38
v0.1.1
f9eac90

Full Changelog: v0.1.0...v0.1.1

v0.1.0

Choose a tag to compare

@github-actions github-actions released this 14 Sep 11:20
v0.1.0
5b2c765