Repository navigation
fix(findrefs,listclass): bound exact-member scan, decode names as MUTF-8 - #44
Merged
MG1937 merged 3 commits intoOct 10, 2026
Merged
Conversation
remove androguard and extract dadguard
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two independent correctness fixes in the dex readers, both surfaced by driving the engine from a front-end that calls these operations without the scoping the GUI always supplies. Independently revertable; kept in one PR only because they share that origin.
1.
fix(findrefs): bound the exact-member scan to the dex tablesThe bug
MethodLocator.locate/FieldLocator.locate, in theclz is None+ precise branch:dex.methods/dex.fieldsareMethodsProxy/FieldsProxy— they define__getitem__and__len__but no__iter__.enumerate()therefore falls back to the legacy sequence protocol, which stops only onIndexError, andget_method()/get_field()synthesise an object for any index and never raise. The comprehension walks off the end of the table into whatever follows it.Measured on a 2-method fixture:
A 7 GB offset read out of a 444-byte buffer. The non-determinism is arguably worse than the crash: when the adjacent bytes happen to parse, the branch returns ids that were never in the table instead of failing.
Who can hit it
Only an exact member search with no class scope —
{"method": {"class": None, "method": [name, True]}}. The GUI cannot reach it: thexhandler always setssearch_class_varfrom the reference's declaring class before_start_search(exact_member=True), and the search box passesexact_member=False. The two existing tests (test_exact_{method,field}_locator_does_not_use_substring_matching) both supply a class, so they cover only the_match_clz_midspath.It is reachable for any caller that treats
exact_memberas a standalone flag — e.g. "find every call to a member named X anywhere in the APK", which is the natural way to use the feature.The fix
len(proxy)isheader.methods[1]/header.fields[1]— the same size_build_mapalready iterates — so the domain of returned ids is unchanged; only the walk is bounded. Class-scoped paths are untouched (clz_midsis a real set and was always safe).2.
fix(listclass): decode class names as MUTF-8The bug
ApkHandler.list_classesdecodesstring_data_itempayloads withraw_name.decode("utf-8", errors="replace"). DEX string data is MUTF-8: a code point above the BMP is stored as a surrogate pair (CESU-8), which strict UTF-8 rejects, so those bytes all collapse to U+FFFD.DEX.get_stringhas decoded MUTF-8 since #33, solistclassnamed a class differently fromgetclass/findrefs:listclassbeforeDEX.get_string)Ll/\U00010000;Ll/\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd;Ll/\U00010000;Scope
Only names containing a non-BMP code point change; everything else is byte-identical:
Lexample/Test;Ll/\u06e1;, the #33 case)Ll/\u06e1;Ll/\U00010000;)L\ufffdbad;The fallback is the same decoder
tinydexuses, so a listing now agrees withDEX.get_stringon well-formed and malformed input. Note the project's two MUTF-8 decoders disagree on malformed input —mutf8.decode_modified_utf8keeps the offending byte,_decode_mutf8_fallbacksubstitutes U+FFFD — which is why this reusestinydexrather than themutf8dependency added in efa3457.The fallback import is deferred into the method, following the existing convention in
for_each_findrefs, so thegetclasspath pays nothing. The strict fast path is also marginally faster thanerrors="replace"(13.9 ms vs 18.4 ms per 200k names).Testing
python tests/run_tests.py --require-decompiler→ 107 tests, OK, no skips.Neither branch has coverage in the test tree today — the astral-name case and the no-class exact search are exercised only in a front-end's own test file that is not part of this PR. I'm happy to add:
tests/test_listclass.py: fixture with a non-BMP class name, asserting the listing matchesDEX.get_string.tests/test_source_edit.py(ortest_references.py):locate()withclassomitted and precise, asserting it returns the table ids and does not wander.Follow-up, not in this PR
getclassstill cannot resolve a class whose name is non-BMP:_find_type_idx/get_class_dexbinary-search the type table comparingdalvik_class.encode("utf-8")against the stored CESU-8, soF0 90 80 80never matchesED A0 80 ED B0 80. Fix 2 makes the listing report such a name correctly, but it still can't be fed back intogetclass. That means touching thegetclassfast path, which sits under the startup budget, so it belongs in its own change.