The Python `droidasc` indexer works and is one `pip install` away, but the same project has a Rust
re-implementation on its `rust` branch, and this pass answers whether it can be used instead of the
Python one. It can, with one documented caveat. Everything below was measured here; nothing is taken
from the upstream README's numbers.
**What was built.** `rasc` publishes no artifact -- no release asset, and `cargo install rasc`
fetches an unrelated maths parser -- so adopting it means building it. rustup (gnu host, rustc 1.98.1)
plus a 64-bit MinGW-w64 gcc 16.1.0 as the linker, `cargo build --release` in **117 s**, producing a
2.13 MB binary from `MG1937/ASC@e809c7b` (rust branch, sha256 `1becdb86…c0264b`). The build traps are
recorded because they cost time here: the host's existing MinGW is **32-bit** and fails with
`sorry, unimplemented: 64-bit mode not compiled in`, E:\vs2022 has no VC tools, and the registered WSL2
distribution cannot start (`HCS_E_SERVICE_NOT_AVAILABLE`) -- so neither the MSVC nor the Linux path
was available.
**What it answers.** Same CLI shape as the Python tool (`classes` / `manifest` / `getclass` /
`findrefs {string,type,method,field}`), and on this repository's archives the answers match:
MASTG UnCrackable-Level3 (1.4 MB) classes 5.6x findrefs string 15.6x findrefs type 16.2x
a real 34.8 MB app (30,768 classes) classes 4.0x -- class sets IDENTICAL, 0 differences either way
`scripts/rasc_build.py` wraps the whole path -- `--check`, `--build`, `--verify <apk>` -- so the
verification travels with the kit instead of living in a note: `--verify` compares the class-definition
set against `droidasc` and fails on any difference. It resolves the repository root by walking up
rather than assuming a layout, prepends a non-default `CARGO_HOME` to PATH, and reports a missing
toolchain as exit 3 with the install step.
**The blind spot, and why the rule is a rule.** The rasc project ships its own JADX-parity harness.
Run here on a fair sample (app-like classes; the harness refuses to judge a class JADX cannot
decompile), 17 of 20 were judged and 16 matched literal-for-literal with 0 reorderings. The one
difference is not cosmetic: `org.bouncycastle.crypto.PasswordConverter` is an enum whose constants
override a method, JADX emits the three anonymous subclasses with their `convert` / `getType` bodies,
and **rasc emits a bare constant list -- the bodies are gone and no warning is emitted**. Clean,
plausible, incomplete output is precisely the failure shape this repository catalogues, so:
use rasc to locate, and to read ordinary classes; confirm an enum that shows no bodies with a
second reader; keep droidasc and JADX as the cross-checks the skill already requires.
The automated verdict said "literals rasc lost: 1", which understates it -- counting literals cannot
see a dropped body. That is recorded in the evidence file rather than smoothed over.
**What changed in the kit.** `references/rasc-and-droidsaw.md` (the measurements, the failing class,
the build traps, provenance); `scripts/rasc_build.py`; `toolchain.md` Tier 1 now lists `rasc` first
with `droidasc` as the fallback and the cross-check; README structure and script tables; the evidence
record `docs/tool-verification/EXTENSION-rasc.md`.
**One inconsistency this exposed and fixed.** `check_repo.py` looked for a reference or script name in
`SKILL.md` + the README only, while this repository's reference inventory lives in
`references/routing.md` -- so a correctly registered file was reported as orphaned, and the script
half of that check was not actually running. The registration surface is now SKILL.md + README +
`references/routing.md`, nested reference directories are read too, and a planted unregistered script
was used to confirm the check still fires.
Gates: check_repo 0 problem, check_refs 0 dangling / 0 warning, check_routing coherent, check_budget
0 failure / 0 warning, check_commands 154 checked / 0 drift, pytest 625 passed / 163 skipped /
25 xfailed.
7.1 KiB
EXTENSION — rasc (the Rust ASC) measured against the Python one
Evidence for skills/apk-reverse/references/rasc-and-droidsaw.md. Every number below has the command
that produced it.
What was measured
| Claim | Grade | Result |
|---|---|---|
rasc builds from the rust branch on this host with no prebuilt artifact |
observed | rustc 1.98.1 (gnu host) + MinGW-w64 gcc 16.1.0; cargo build --release 117 s; binary 2,236,416 B |
It is faster than the Python droidasc on the same archives |
observed | 4.0–5.6× for classes, up to 16.2× for findrefs (tables below) |
| It returns the same answers | observed | identical class-definition sets, 30,768 / 30,768 with 0 differences in either direction; findrefs string identical on one sample, type a superset (390 vs 384) |
Its getclass decompiles ordinary classes correctly |
observed | 16 of 17 JADX-judged classes matched literal-for-literal, 0 reorderings |
| It silently drops the bodies of enum constants that override a method | observed | org.bouncycastle.crypto.PasswordConverter: JADX emits three anonymous subclasses with convert/getType; rasc emits a bare constant list, no warning |
| Its own benchmark's 8.0× geometric mean reproduces here | not reproduced, and explained | that figure is from a 343 MiB / 567k-class archive; on a 34.8 MB / 30,768-class app the ratio is 4×. Both are real; the small-archive figure is what a normal task hits |
Environment
Host Windows 11. Device not involved. Toolchain installed for this pass, into the gitignored
tools/_work/:
rustup-init (x86_64-pc-windows-gnu) -> rustc 1.98.1, cargo 1.98.1
winget install BrechtSanders.WinLibs.POSIX.UCRT -> gcc 16.1.0 (x86_64-w64-mingw32, UCRT)
The 32-bit MinGW already on this host (C:\MinGW, gcc 6.3.0, target mingw32) cannot link a
64-bit binary — gcc -m64 reports sorry, unimplemented: 64-bit mode not compiled in, and the Rust
GNU target needs a 64-bit linker. E:\vs2022 has no VC tools (cl.exe / link.exe absent), and the
WSL2 Ubuntu distribution is registered but HCS_E_SERVICE_NOT_AVAILABLE, so no Linux path either.
$ python skills/apk-reverse/scripts/rasc_build.py --check
toolchain : git=E:\Git\cmd\git.EXE cargo=...\rust\cargo\bin\cargo.EXE rustc=...\cargo\bin\rustc.EXE gcc=C:\MinGW\bin\gcc.EXE(mingw32)
rasc : E:\apk-reverse\tools\_work\rust\target\release\rasc.exe rasc 0.1.0
result : ok
Speed and agreement
Fastest of 3 runs (1 run on the largest), same machine, PYTHONUTF8=1 for the Python side.
$ python tools/_work/compare_asc.py tools/_work/bench/apks/UnCrackable-Level3.apk --repeat 3
scenario rasc s py s speedup result agreement
classes 0.025 0.138 5.6 equal rasc=1396 py=1396
manifest 0.017 - - not comparable
findrefs string 0.022 0.341 15.6 overlap 100% rasc=3 py=3
findrefs type 0.022 0.358 16.2 overlap 98% rasc=390 py=384
Rounded to whole numbers in the reference file (4.7–14.7× there vs 5.6–16.2× here) because these are two independent runs of the same measurement; both are in the observed range, and the run-to-run spread on a sub-100 ms command is the honest resolution.
On a real 34.8 MB application (MT-Manager.apk, 30,768 classes):
$ python -c "... rasc classes vs droidasc listclass, set comparison ..."
rasc : 0.048 s 30768 classes
droidasc : 0.190 s 30768 classes
speedup : 4.0x
identical set : True
only in rasc : 0 only in droidasc: 0
python skills/apk-reverse/scripts/rasc_build.py --verify tools/_phone-modules/MT-Manager.apk runs
the same comparison and reports {'result': 'ok', 'rasc_classes': 30768, 'droidasc_classes': 30768, 'only_rasc': 0, 'only_droidasc': 0}, so the check travels with the kit rather than living in a note.
A defect found on the Python side during this work: droidasc listclass exits 1 with
'gbk' codec can't encode character on an app whose classes contain non-Latin identifiers, when the
console encoding is not UTF-8. With PYTHONUTF8=1 it completes. rasc was unaffected. That is a
one-variable difference in the environment, not a property of either tool, and it is recorded because
it cost a round here.
The blind spot, verified rather than reported
The rasc repository ships bench/jadx_parity.py, which treats a string literal JADX has and rasc
does not as lost code, and refuses to judge a class JADX itself could not decompile cleanly. Its own
class sampling takes every-nth entry from rasc classes, which on a real app lands mostly on
obfuscated names where JADX produces nothing — 8 of 10 picked that way were unjudgeable here, so the
sample measures the sampler. tools/_work/parity_fair_sample.py filters to app-like classes first
(ASCII, dotted, no inner class, not R/BuildConfig) and feeds those to the unmodified harness:
$ python tools/_work/parity_fair_sample.py tools/_phone-modules/MT-Manager.apk 20
classes in APK: 30768
app-like candidates: ...
judged 17 class(es) where JADX was clean; literals rasc lost: 1; reorderings: 0
The one finding, checked by hand with tools/_work/verify_parity_finding.py:
class : org.bouncycastle.crypto.PasswordConverter
jadx literals: ['ASCII', 'PKCS12', 'UTF8']
rasc literals: []
missing : ['ASCII', 'PKCS12', 'UTF8']
'ASCII' appears in rasc output as an identifier: True
...
VERDICT: form difference (enum constants printed as a list, literals not re-emitted)
The automated verdict understates it, and that is the finding. The harness counts literals; the
human check shows the literals are gone because the whole anonymous subclass body is gone — JADX
prints ASCII { ... convert() ... getType() ... } and rasc prints ASCII,. rasc emits no
comment, no TODO, no warning. Output that is clean, plausible and incomplete is the failure shape this
repository's pitfalls.md exists to catalogue, so the consequence is a rule, not a caveat: confirm an
enum with no bodies using a second reader.
Not established
- No whole-APK parity run. The comparison is on class sets, sampled
getclassoutput and onefindrefsfamily per sample.bench/corpus_parity.pyandbench/jadx_parity.pyexist for a fuller run and were used on one archive, not on a corpus. findrefson a large corpus was not run to completion. On the 34.8 MB app,findrefs string httpreturned 0 rows on both sides — a query with no hits is not a parity result, and a query that does hit was only compared on the small archive.getclasswas judged on 17 classes of one app. The enum blind spot is confirmed; whether other class shapes (inner classes, synthetic bridges,invokedynamic) diverge the same way is untested.- No device or Android-side use.
rascreads archives on the host; nothing here exercises it as a device tool, and the reference file makes no claim about that. - The 8.0× upstream figure was not reproduced, because the archive it was measured on (343 MiB, 567k classes) is not available here. The direction and the smaller ratio were measured.