{"record":{"id":"a1afa465faf2995c","repo":"Tencent/tinker","slug":"bad-oat-version","errorCode":null,"errorMessage":"Bad oat version: {}","messagePattern":"Bad oat version: (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/shareutil/ShareOatUtil.java","lineNumber":85,"sourceCode":"                    || oatMagicAndVersion[3] != '\\n') {\n                throw new IOException(\n                        String.format(\"Bad oat magic: %x %x %x %x\",\n                                oatMagicAndVersion[0],\n                                oatMagicAndVersion[1],\n                                oatMagicAndVersion[2],\n                                oatMagicAndVersion[3])\n                );\n            }\n\n            final int versionOffsetFromOatBegin = 4;\n            final int versionBytes = 3;\n\n            final String oatVersion = new String(oatMagicAndVersion,\n                    versionOffsetFromOatBegin, versionBytes, Charset.forName(\"ASCII\"));\n            try {\n                Integer.parseInt(oatVersion);\n            } catch (NumberFormatException e) {\n                throw new IOException(\"Bad oat version: \" + oatVersion);\n            }\n\n            ByteBuffer buffer = ByteBuffer.allocate(128);\n            buffer.order(elfFile.getDataOrder());\n            // TODO This is a risk point, since each oat version may use a different offset.\n            // So far it's ok. Perhaps we should use oatVersionNum to judge the right offset in\n            // the future.\n            final int isaNumOffsetFromOatBegin = 12;\n            channel.position(roDataHdr.shOffset + isaNumOffsetFromOatBegin);\n            buffer.limit(4);\n            ShareElfFile.readUntilLimit(channel, buffer, \"Failed to read isa num.\");\n\n            int isaNum = buffer.getInt();\n            if (isaNum < 0 || isaNum >= InstructionSet.values().length) {\n                throw new IOException(\"Bad isa num: \" + isaNum);\n            }\n\n            switch (InstructionSet.values()[isaNum]) {","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/Tencent/tinker/blob/1b7ea02c239840f563ea64fb5bd286eb98d4011e/tinker-android/tinker-android-loader-no-op/src/main/java/com/tencent/tinker/loader/shareutil/ShareOatUtil.java#L67-L103","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nString oatVersion = new String(oatMagicAndVersion, 4, 3, Charset.forName(\"ASCII\"));\nInteger.parseInt(oatVersion); // NumberFormatException -> IOException\n\n// after\nString oatVersion = new String(oatMagicAndVersion, 4, 3, Charset.forName(\"ASCII\")).trim();\nif (!oatVersion.matches(\"\\\\d{3}\")) {\n    throw new IOException(\"Bad oat version: \" + oatVersion);\n}","handlingStrategy":"try-catch","validationCode":"byte[] head = new byte[8];\ntry (RandomAccessFile raf = new RandomAccessFile(oatFile, \"r\")) {\n    raf.seek(roDataOffset); raf.readFully(head);\n    String v = new String(head, 4, 3, \"ASCII\");\n    if (!v.matches(\"\\\\d{3}\")) { /* stale odex, skip */ }\n}","typeGuard":null,"tryCatchPattern":"catch (IOException e) when message starts with 'Bad oat version' -> delete odex and fall back to abi-based instruction set","preventionTips":["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."],"tags":["oat","odex","parsing","android"],"backgroundTag":null,"analyzedSha":"1b7ea02c239840f563ea64fb5bd286eb98d4011e","analyzedAt":"2026-08-14T15:16:52.110Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}