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

  1. Treat the odex as stale: delete it and let tinker regenerate it via dex2oat, then retry.
  2. Check the version field manually (odex bytes 4..6) to see whether the file is truncated or from an incompatible ART version.
  3. 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.
  4. 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

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


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