Re-ran the documented comparison end to end (11/11 scenarios valid, randomized
interleave, median of N, threads=8):
findrefs 6 queries 117-191 ms vs 830-1978 ms 7.1x - 11.5x
getclass early/late/miss 45/77/62 ms vs 124/259/245 ms 2.7x / 3.4x / 4.0x
manifest 13 ms vs 268 ms 20.3x
classes 98 ms vs 2318 ms 23.7x
geometric mean 8.0x
Up from 5.4-5.8x at the start of the session; getclass early, the only scenario that
used to lose (0.6x), is now 2.7x. Parity is unchanged: classes 567192/567192 and the
same handful of known reference-side row differences.
The rank-and-file fix: replacing the application-specific query during the privacy pass
left one scenario asking for a literal that does not occur in this archive (0/0 rows,
reported INVALID), and the scenario builder's first_class() helper called a function
that does not exist in this script. The scenario now uses a neutral literal with equal
row counts on both sides (okhttp, 126/126), and first_class() runs subprocess directly.
A literal that lands in the wrong place is invisible to a class-wide literal set,
so each method's literals are now compared in order as well: a relative-order
inversion among the literals both sides have is reported as REORDERED.
Three-way check of the reported site in a real services.jar (124 DEX entries,
139,503 classes):
rasc : v0_202.enforceCallingPermission("android.permission.START_ACTIVITIES_FROM_BACKGROUND", "BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS")
asc : this.mAm.enforceCallingPermission("android.permission.START_ACTIVITIES_FROM_BACKGROUND", "BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS")
JADX : the whole statement is missing from its output for that class
rasc and asc agree with the source semantics; JADX drops the call. In the sampled
classes the ordered check reports zero reorderings, so no value misplacement was
found in rasc. One missing statement remains - DiskStatsService#reportFreeSpace
loses the pw.print("-Error: ") branch; the pre-patch binary reproduces it, so it is
a decompiler completeness gap rather than the class-scoped parse.
JADX is the authority for decompiled source, so the comparison is anchored to it:
per-class string-literal sets, judged only when JADX's own output is clean (no
bad-code markers) and the emitted declaration matches the requested class (JADX's
--single-class resolves an inner $Name to the outer class, which produced two
false alarms before the check existed).
On a real 77.9 MiB services.jar (124 DEX entries, 139,503 classes) the sampled
classes agree, with one confirmed difference: in
com.android.server.DiskStatsService#reportFreeSpace, JADX's
catch (IllegalArgumentException) branch prints "-Error: ", while rasc's
droidsaw output drops that branch statement. Verified with the pre-patch binary
(full parse) that this is unrelated to the class-scoped parse: it is a
completeness gap in the decompiler itself.
JADX's own limits are recorded too: the same ActiveServices class carries 295
bad-code markers and 8 errors, and many $$ExternalSyntheticLambda classes do not
decompile, so those classes must be excluded before comparing.
Found by an independent review and by differential testing against the
reference implementation (a reference, not ground truth):
- droidsaw-dex vendored with a class-scoped parse: getclass 0.6x -> 1.4x,
10-scenario geometric mean 5.8x -> 6.8x; 2070-class equivalence evidence
- DEX 041: getclass was completely broken (0x78-byte header), container memory
1.34 GB -> 3.4 MB, single-member naming, standard-header view for the
decompiler
- manifest: unknown value types no longer fail the whole document, resource-map
chunk validation, exact radix multiples instead of rounded ones
- ZIP: duplicate-entry handling, declared-size-driven allocations bounded
- scanner: per-class_def type-id resolution; no rasc-only rows in the ~770k
rows checked against the reference
Tests 19 -> 60: ZIP codec and end-to-end fixtures, DEX 041 containers, a
hand-built AXML fixture (and the decoder crash it exposed), corpus-driven
fixes, malformed-input sweeps, self-containment, and the decompiler path.
Structure: query model extracted from the CLI layer, zip/bytes/dex submodules,
the scanner returns records instead of formatted text, module docs, and
PATCHES.md for the vendored decoders.
Gates, all runnable from the repository: bench/contracts.sh (output contract,
determinism, dotted descriptors, closed pipes), bench/corpus_parity.py,
bench/mutation_check.py, bench/decompile_equivalence.py, bench/row_oracle.py.