7 Commits
Author SHA1 Message Date
Tsingshui e809c7b4b4 docs: add bilingual README and inline performance results 2026-09-12 03:02:33 +08:00
Tsingshui c3f9ee8d3c Refresh the reference comparison: 8.0x geometric mean, and fix the scenario the scrub broke
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.
2026-09-12 01:41:30 +08:00
Tsingshui 1d3fc2899f jadx_parity: ordered per-method literal check, and the three-way result for the reported string
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.
2026-09-12 00:15:52 +08:00
Tsingshui 8e4fe7a189 JADX-anchored decompilation parity: new bench/jadx_parity.py and the first confirmed difference
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.
2026-09-12 00:09:35 +08:00
Tsingshui cd3fa39221 Correctness: vendored patches, DEX 041, robustness and parity fixes
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
2026-09-11 23:55:38 +08:00
Tsingshui 1911a2bd40 Quality: tests, module split, documentation, in-repo gates
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.
2026-09-11 23:54:58 +08:00
Tsingshui 1142911389 Initial commit: rasc, native Rust APK/DEX analysis CLI (CLI-only) 2026-09-11 23:54:58 +08:00