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