{"record":{"id":"63f07e97796409e4","repo":"Tencent/tinker","slug":"rest-bytes-insufficient-expect-to-read-byte","errorCode":null,"errorMessage":"{} Rest bytes insufficient, expect to read {} bytes but only {} bytes were read.","messagePattern":"(.+?) Rest bytes insufficient, expect to read (.+?) bytes but only (.+?) bytes were read\\.","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareElfFile.java","lineNumber":116,"sourceCode":"            } else {\n                return FILE_TYPE_OTHERS;\n            }\n        } finally {\n            if (is != null) {\n                try {\n                    is.close();\n                } catch (Throwable thr) {\n                    // Ignored.\n                }\n            }\n        }\n    }\n\n    public static void readUntilLimit(FileChannel channel, ByteBuffer bufferOut, String errMsg) throws IOException {\n        bufferOut.rewind();\n        int bytesRead = channel.read(bufferOut);\n        if (bytesRead != bufferOut.limit()) {\n            throw new IOException(errMsg + \" Rest bytes insufficient, expect to read \"\n                    + bufferOut.limit() + \" bytes but only \"\n                    + bytesRead + \" bytes were read.\");\n        }\n        bufferOut.flip();\n    }\n\n    public static String readCString(ByteBuffer buffer) {\n        final byte[] rawBuffer = buffer.array();\n        int begin = buffer.position();\n        while (buffer.hasRemaining() && rawBuffer[buffer.position()] != 0) {\n            buffer.position(buffer.position() + 1);\n        }\n        // Move to the start of next cstring.\n        buffer.position(buffer.position() + 1);\n        return new String(rawBuffer, begin, buffer.position() - begin - 1, Charset.forName(\"ASCII\"));\n    }\n\n    public FileChannel getChannel() {","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/Tencent/tinker/blob/1b7ea02c239840f563ea64fb5bd286eb98d4011e/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareElfFile.java#L98-L134","documentation":"ShareElfFile.readUntilLimit reads exactly bufferOut.limit() bytes from a FileChannel; if channel.read returns fewer bytes (short read at end of file), an IOException is thrown with the caller-supplied errMsg prefix plus 'Rest bytes insufficient'. It means the file being parsed as ELF/OAT is truncated relative to the structure the parser expects.","triggerScenarios":"Parsing an ELF/OAT file whose declared header sizes (e.g. e_phoff/e_shoff-derived offsets) or section sizes extend past the physical end of file — readUntilLimit is used for the ELF header tail, program headers, section headers, and OAT magic/isa reads.","commonSituations":"Corrupted or partially-downloaded odex/oat/so files on device; an ELF produced by a toolchain the parser does not model exactly; reading a file while another process (dexopt, instald) is still writing it; sparse file that failed to sync after OTA.","solutions":["Check the file size against the ELF structure before parsing (read the header first and validate offsets/sizes against file length).","Re-delete the corrupted odex/oat artifact so the system regenerates it (or reinstall the app), then retry patch load.","If this happens during OTA-related odex checks (ShareOatUtil), disable odex-compatible patching or align patching with the OTA state via isSystemOTA checks.","Report the exact file path from the log and inspect it with readelf on a pulled copy to confirm truncation."],"exampleFix":"// before\nint bytesRead = channel.read(bufferOut);\n// after (loop until limit or EOF, then fail with the same message)\nint bytesRead = 0;\nwhile (bytesRead < bufferOut.limit()) {\n    int n = channel.read(bufferOut);\n    if (n < 0) break;\n    bytesRead += n;\n}","handlingStrategy":"validation","validationCode":"// Check file is long enough before ELF/OAT parsing\nlong need = headerSizeEstimate; // e.g. 64 for ELF64 ehdr\nif (!file.exists() || file.length() < need) {\n    throw new IOException(\"file too short: \" + file);\n}","typeGuard":null,"tryCatchPattern":"try {\n    elfFile = new ShareElfFile(file);\n} catch (IOException e) {\n    // truncated or unreadable ELF: delete artifact so it regenerates, skip oat logic\n}","preventionTips":["Verify file length and md5 before parsing ELF/OAT structures.","Do not parse odex files while dexopt/installd may still be writing them.","Treat any short-read failure as corruption and regenerate the artifact."],"tags":["android","tinker","elf","oat","truncated-file","io"],"backgroundTag":null,"analyzedSha":"1b7ea02c239840f563ea64fb5bd286eb98d4011e","analyzedAt":"2026-08-14T15:16:52.110Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}