Skip to content

v0.1.6

Choose a tag to compare

@github-actions github-actions released this 27 Sep 14:59
· 27 commits to main since this release
v0.1.6
b98df69

A verification release: the claims now rest on real applications with known source, not only on artifacts we built ourselves.

New gate: tests/app_truth.rs

Every existing truth gate ran on our own inputs — source_truth compiles a 160-line fixture, ground_truth diffs against .symtab on hello-world samples. This one decompiles real applications whose source is known and judges the output against that source, which is the only check that can catch a recovery chain rotting at scale.

Corpus: locally built flutter-samples apps (DAE_DEMO_ROOT, defaulting to a sibling checkout) — material_3_demo at 5,107 source lines and animations at 2,108. Measured, and asserted with a 0.90 floor:

check material_3_demo animations
public classes/mixins/enums in lib/ recovered 85/86 = 98.8% 35/35 = 100%
source string literals present in the output 292/306 = 95.4% 111/114 = 97.4%
source files mapping to a recovered library 18/18 = 100% 21/23 = 91%

The single missed type is enum Value { first, second } — its library is in the output, so the enum was tree-shaken rather than misparsed. The two unmapped animations files are examples nothing references.

The fast half (plain export + source comparison) takes ~2 s and runs in the normal suite; the --decompile + dart analyze half is #[ignore]d at 247 s for both. Negative-tested by raising the class floor to 1.01, which fails and names Value.

Also measured on shipping apps

  • Reqable.app (macOS arm64, 26 MB, Dart 3.3.4, a verified profile): 70,996 table entries, 0 warnings, 1,808 functions at 94.9% structured, dart analyze 0 errors.
  • material_3_demo: 15,082 functions, 985,900 statements, 92.5% structured, 3 unmapped lines, 0 errors, 94.7% of direct calls resolved to a name.
  • animations: 11,102 functions, 696,396 statements, 92.5% structured, 3 unmapped lines, 0 errors.

Added to the five Android builds already matching aotopsy exactly (57,960 / 79,327 / 30,782 / 19,752 / 22,623, all at 0 warnings).

What is out of scope, stated plainly

Of the 41 APKs in the local corpus exactly 8 ship a lib/arm64-v8a/libapp.so. Three of those eight are not standard Flutter AOT snapshots, and dae says so rather than guessing:

  • WeChat — its libapp.so is a 21-byte CSOS placeholder inside the APK itself (confirmed with unzip -l, not an extraction mistake). The real payload lives elsewhere.
  • DingTalk — features string carries enable_aion + llvm_compiler: a vendor fork that replaced the Dart AOT compiler with an LLVM backend. Its snapshot version hash matches no known SDK, so detection falls back to a low-confidence structural probe.
  • Tonghuashun — genuine Dart 2.7.2, and it behaves identically to the reference hello_2.7.2 sample: strings and the object layer export, but the instruction table is recoverable neither from the snapshot header nor from Code-cluster text offsets, so there are no function addresses. That is the documented ≤2.9 ceiling, not an artifact-specific failure.

Two allocation reductions — and an honest null result

Both verified byte-identical over 985,900 statements:

  • replace_word copied its input one byte at a time via out.push(b[i] as char) — a char conversion plus a UTF-8 encode per byte. It now moves whole slices with push_str, and subst_regs skips the call entirely when the operand text cannot contain the register.
  • Three output buffers grew from zero (per-function body, per-function disassembly comment, per-library file); all three are now sized from the statement / instruction / function counts.

Neither produced a measurable speedup, and we are not claiming one. Profiling a debug-symbol build attributed the time to format_inner, Formatter::pad / pad_integral, RawVecInner::finish_grow and _platform_memmove — i.e. the ~1M format!("... // {addr:#x}") calls, not the register substitution first suspected. Interleaved A/B on animations gave 67.7 / 61.5 s before and 63.1 / 67.7 s after: the same binary varies by more than the effect on a host carrying unrelated load. The changes stay because they are strictly less work and provably output-identical. The real fix — replacing those format! calls with write! into reused buffers across render_op — is a larger refactor, deliberately not attempted in a release commit.

Docs

README and DECOMPILER carry the real-app tables above and the out-of-scope classification. The gates list now names app_truth first, since it is the strongest one.

Verified

48 tests under DAE_REQUIRE_GATES=1 · full scorecard 26 artifacts / 0 errors · regress_all 25/25 byte-identical · check_profiles 47/47 · clippy 0 · Lark / Weibo / Reqable.app still at 79,327 / 22,623 / 70,996 table entries.

Install

cargo install dae-rs          # crate name is dae-rs; the binary is `dae`
brew install ejfkdev/tap/dae
scoop bucket add ejfkdev https://github.com/ejfkdev/scoop-bucket; scoop install dae

Or grab a binary for Linux / macOS / Windows (x64 and arm64) below.