{"record":{"id":"f656d56653cafca3","repo":"Tencent/tinker","slug":"declared-length-expectedlength-doesn-t-match-de","errorCode":null,"errorMessage":"Declared length ${expectedLength} doesn't match decoded length of ${decodedLength}","messagePattern":"Declared length (.+?) doesn't match decoded length of (.+?)","errorType":"exception","errorClass":"DexException","httpStatus":null,"severity":"error","filePath":"third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/io/DexDataBuffer.java","lineNumber":176,"sourceCode":"    public int readUleb128() {\n        return Leb128.readUnsignedLeb128(this);\n    }\n\n    public int readUleb128p1() {\n        return Leb128.readUnsignedLeb128(this) - 1;\n    }\n\n    public int readSleb128() {\n        return Leb128.readSignedLeb128(this);\n    }\n\n    public StringData readStringData() {\n        int off = data.position();\n        try {\n            int expectedLength = readUleb128();\n            String result = Mutf8.decode(this, new char[expectedLength]);\n            if (result.length() != expectedLength) {\n                throw new DexException(\"Declared length \" + expectedLength\n                        + \" doesn't match decoded length of \" + result.length());\n            }\n            return new StringData(off, result);\n        } catch (UTFDataFormatException e) {\n            throw new DexException(e);\n        }\n    }\n\n    public TypeList readTypeList() {\n        int off = data.position();\n        int size = readInt();\n        short[] types = readShortArray(size);\n        return new TypeList(off, types);\n    }\n\n    public FieldId readFieldId() {\n        int off = data.position();\n        int declaringClassIndex = readUnsignedShort();","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/Tencent/tinker/blob/1b7ea02c239840f563ea64fb5bd286eb98d4011e/third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/io/DexDataBuffer.java#L158-L194","documentation":"Thrown by DexDataBuffer.readStringData() while decoding a string item from a dex file's string data section. Every MUTF-8 string is prefixed with a uleb128-declared character count; after Mutf8.decode fills the buffer, the decoded String's length() must equal that declared count. A mismatch means the string data section is corrupt, truncated, or was written by a non-conforming tool, so the buffer cannot safely advance past this item.","triggerScenarios":"Calling readStringData() (directly or via any dex parser that walks the string ids / string data region, e.g. when tinker loads, compares, or rewrites a dex). It fires when the uleb128 length prefix at the current position disagrees with the number of characters the MUTF-8 bytes actually decode to, including cases where the position pointer landed mid-string because an earlier field was misparsed.","commonSituations":"A dex truncated or byte-flipped during patch download/apply; a dex produced or post-processed by a tool (obfuscator, packer, dex merger) that emits non-standard string data; applying a tinker patch to an old dex that does not byte-match the one the patch was generated from; wrong offset arithmetic in custom code that repositions the buffer before reading string data.","solutions":["Verify the dex is intact: run the Android build-tools dexdump (or `dexdump -d classes.dex`) on the same file; if it also fails, the dex itself is corrupt — rebuild/regenerate it.","Confirm the old-apk/dex used for patch generation is byte-identical (compare CRC/SHA-1 of the dex header) to the one the patch is applied to; regenerate the patch from the correct base.","If the dex comes from your own pipeline (merge/repackage step), update or replace the tool that wrote the offending string data and regenerate the artifact.","Upgrade tinker/aosp-dexutils to the latest version in case the decoder was fixed for an MUTF-8 edge case, and report the failing dex sample to the tinker project if it persists."],"exampleFix":"// before: applying a patch against an unverified base dex\nbyte[] patched = BSPatch.patch(oldDex, patch);\nnew Dex(patched).computeSizes(); // may throw DexException from readStringData\n\n// after: verify the base artifact matches what the patch was built from\nif (!sha1(oldDex).equals(expectedOldDexSha1)) {\n    throw new IllegalStateException(\"base dex does not match patch metadata\");\n}\nbyte[] patched = BSPatch.patch(oldDex, patch);","handlingStrategy":"validation","validationCode":"// Verify the dex header's checksum+signature before parsing it\nboolean dexIntact(byte[] dex) {\n    java.nio.ByteBuffer b = java.nio.ByteBuffer.wrap(dex).order(java.nio.ByteOrder.LITTLE_ENDIAN);\n    byte[] magic = new byte[8]; b.get(magic);\n    if (!new String(magic, 0, 4).equals(\"dex\\u0000\")) return false;\n    java.security.MessageDigest sha1;\n    try { sha1 = java.security.MessageDigest.getInstance(\"SHA-1\"); }\n    catch (Exception e) { return false; }\n    sha1.update(dex, 32, dex.length - 32);\n    byte[] sig = new byte[20]; b.position(12); b.get(sig);\n    return java.util.Arrays.equals(sig, sha1.digest());\n}","typeGuard":null,"tryCatchPattern":"try {\n    StringData sd = buffer.readStringData();\n} catch (com.tencent.tinker.android.dex.DexException e) {\n    // treat as unrecoverable data corruption: quarantine the dex, do not retry with the same bytes\n    throw new IllegalArgumentException(\"corrupt dex string data: \" + e.getMessage(), e);\n}","preventionTips":["Digest-verify dex files (SHA-1 of everything after the signature field) before parsing.","Never hand-read at arbitrary offsets into string data; walk the string_ids table instead.","Keep patch-generation and patch-apply built from byte-identical base artifacts."],"tags":["dex","mutf8","uleb128","parsing","tinker"],"backgroundTag":null,"analyzedSha":"1b7ea02c239840f563ea64fb5bd286eb98d4011e","analyzedAt":"2026-08-14T15:16:52.110Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}