Tencent/tinker · error · DexException

Declared length ${expectedLength} doesn't match decoded leng

Error message

Declared length ${expectedLength} doesn't match decoded length of ${decodedLength}

What it means

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.

Source

Thrown at third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/io/DexDataBuffer.java:176

    public int readUleb128() {
        return Leb128.readUnsignedLeb128(this);
    }

    public int readUleb128p1() {
        return Leb128.readUnsignedLeb128(this) - 1;
    }

    public int readSleb128() {
        return Leb128.readSignedLeb128(this);
    }

    public StringData readStringData() {
        int off = data.position();
        try {
            int expectedLength = readUleb128();
            String result = Mutf8.decode(this, new char[expectedLength]);
            if (result.length() != expectedLength) {
                throw new DexException("Declared length " + expectedLength
                        + " doesn't match decoded length of " + result.length());
            }
            return new StringData(off, result);
        } catch (UTFDataFormatException e) {
            throw new DexException(e);
        }
    }

    public TypeList readTypeList() {
        int off = data.position();
        int size = readInt();
        short[] types = readShortArray(size);
        return new TypeList(off, types);
    }

    public FieldId readFieldId() {
        int off = data.position();
        int declaringClassIndex = readUnsignedShort();

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. 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.
  2. 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.
  3. 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.
  4. 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.

Example fix

// before: applying a patch against an unverified base dex
byte[] patched = BSPatch.patch(oldDex, patch);
new Dex(patched).computeSizes(); // may throw DexException from readStringData

// after: verify the base artifact matches what the patch was built from
if (!sha1(oldDex).equals(expectedOldDexSha1)) {
    throw new IllegalStateException("base dex does not match patch metadata");
}
byte[] patched = BSPatch.patch(oldDex, patch);
Defensive patterns

Strategy: validation

Validate before calling

// Verify the dex header's checksum+signature before parsing it
boolean dexIntact(byte[] dex) {
    java.nio.ByteBuffer b = java.nio.ByteBuffer.wrap(dex).order(java.nio.ByteOrder.LITTLE_ENDIAN);
    byte[] magic = new byte[8]; b.get(magic);
    if (!new String(magic, 0, 4).equals("dex\u0000")) return false;
    java.security.MessageDigest sha1;
    try { sha1 = java.security.MessageDigest.getInstance("SHA-1"); }
    catch (Exception e) { return false; }
    sha1.update(dex, 32, dex.length - 32);
    byte[] sig = new byte[20]; b.position(12); b.get(sig);
    return java.util.Arrays.equals(sig, sha1.digest());
}

Try / catch

try {
    StringData sd = buffer.readStringData();
} catch (com.tencent.tinker.android.dex.DexException e) {
    // treat as unrecoverable data corruption: quarantine the dex, do not retry with the same bytes
    throw new IllegalArgumentException("corrupt dex string data: " + e.getMessage(), e);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14). Data as JSON: /api/errors/f656d56653cafca3. Report an issue: GitHub.