Tencent/tinker · error · IOException
Bad oat version: {}
Error message
Bad oat version: {} What it means
After the OAT magic check passes, ShareOatUtil reads 3 ASCII bytes at offset 4 (the OAT version, e.g. "079") and runs Integer.parseInt on them. If those bytes are not a decimal version string, the file's OAT header does not follow the expected layout and an IOException('Bad oat version: <str>') is thrown. It guards against parsing a file whose header format differs from what the fixed-offset parser understands.
Source
Thrown at tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/shareutil/ShareOatUtil.java:85
|| oatMagicAndVersion[3] != '\n') {
throw new IOException(
String.format("Bad oat magic: %x %x %x %x",
oatMagicAndVersion[0],
oatMagicAndVersion[1],
oatMagicAndVersion[2],
oatMagicAndVersion[3])
);
}
final int versionOffsetFromOatBegin = 4;
final int versionBytes = 3;
final String oatVersion = new String(oatMagicAndVersion,
versionOffsetFromOatBegin, versionBytes, Charset.forName("ASCII"));
try {
Integer.parseInt(oatVersion);
} catch (NumberFormatException e) {
throw new IOException("Bad oat version: " + oatVersion);
}
ByteBuffer buffer = ByteBuffer.allocate(128);
buffer.order(elfFile.getDataOrder());
// TODO This is a risk point, since each oat version may use a different offset.
// So far it's ok. Perhaps we should use oatVersionNum to judge the right offset in
// the future.
final int isaNumOffsetFromOatBegin = 12;
channel.position(roDataHdr.shOffset + isaNumOffsetFromOatBegin);
buffer.limit(4);
ShareElfFile.readUntilLimit(channel, buffer, "Failed to read isa num.");
int isaNum = buffer.getInt();
if (isaNum < 0 || isaNum >= InstructionSet.values().length) {
throw new IOException("Bad isa num: " + isaNum);
}
switch (InstructionSet.values()[isaNum]) {View on GitHub (pinned to 1b7ea02c23)
Solutions
- Treat the odex as stale: delete it and let tinker regenerate it via dex2oat, then retry.
- Check the version field manually (odex bytes 4..6) to see whether the file is truncated or from an incompatible ART version.
- If it reproduces on a specific ROM/ART version, disable the odex-based instruction-set lookup and use ShareTinkerInternals.getCurrentInstructionSet() (Build.CPU_ABI based) instead.
- Upgrade tinker — the code's own TODO notes version-dependent offsets; newer releases handle more oat versions.
Example fix
// before
String oatVersion = new String(oatMagicAndVersion, 4, 3, Charset.forName("ASCII"));
Integer.parseInt(oatVersion); // NumberFormatException -> IOException
// after
String oatVersion = new String(oatMagicAndVersion, 4, 3, Charset.forName("ASCII")).trim();
if (!oatVersion.matches("\\d{3}")) {
throw new IOException("Bad oat version: " + oatVersion);
} Defensive patterns
Strategy: try-catch
Validate before calling
byte[] head = new byte[8];
try (RandomAccessFile raf = new RandomAccessFile(oatFile, "r")) {
raf.seek(roDataOffset); raf.readFully(head);
String v = new String(head, 4, 3, "ASCII");
if (!v.matches("\\d{3}")) { /* stale odex, skip */ }
} Try / catch
catch (IOException e) when message starts with 'Bad oat version' -> delete odex and fall back to abi-based instruction set
Prevention
- Regenerate the odex on-device whenever its header does not match the expected format.
- Do not copy odex files between devices or builds.
- Prefer ShareTinkerInternals.getCurrentInstructionSet() when oat parsing is not strictly required.
When it happens
Trigger: An oat file whose magic is 'oat\n' but whose version field contains non-numeric bytes: partially written odex, header from an ART version with different layout, or an offset shifted because roDataHdr.shOffset pointed at non-oat data inside a multi-section ELF.
Common situations: Android OTA introducing a new ART version writing different bytes at that offset; corrupt download of the patch odex; devices with modified ART builds; mixing odex files produced for a different instruction set.
Related errors
- Bad oat magic: %x %x %x %x
- patch dex opt file not found, but path is null!!!!
- Unable to find .rodata section.
- Bad isa num: {}
- No odex file was generated after calling performDexOptSecond
AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14).
Data as JSON: /api/errors/a1afa465faf2995c.
Report an issue: GitHub.