Tencent/tinker · error · IOException

{} Rest bytes insufficient, expect to read {} bytes but only

Error message

{} Rest bytes insufficient, expect to read {} bytes but only {} bytes were read.

What it means

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.

Source

Thrown at tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareElfFile.java:116

            } else {
                return FILE_TYPE_OTHERS;
            }
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (Throwable thr) {
                    // Ignored.
                }
            }
        }
    }

    public static void readUntilLimit(FileChannel channel, ByteBuffer bufferOut, String errMsg) throws IOException {
        bufferOut.rewind();
        int bytesRead = channel.read(bufferOut);
        if (bytesRead != bufferOut.limit()) {
            throw new IOException(errMsg + " Rest bytes insufficient, expect to read "
                    + bufferOut.limit() + " bytes but only "
                    + bytesRead + " bytes were read.");
        }
        bufferOut.flip();
    }

    public static String readCString(ByteBuffer buffer) {
        final byte[] rawBuffer = buffer.array();
        int begin = buffer.position();
        while (buffer.hasRemaining() && rawBuffer[buffer.position()] != 0) {
            buffer.position(buffer.position() + 1);
        }
        // Move to the start of next cstring.
        buffer.position(buffer.position() + 1);
        return new String(rawBuffer, begin, buffer.position() - begin - 1, Charset.forName("ASCII"));
    }

    public FileChannel getChannel() {

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. Check the file size against the ELF structure before parsing (read the header first and validate offsets/sizes against file length).
  2. Re-delete the corrupted odex/oat artifact so the system regenerates it (or reinstall the app), then retry patch load.
  3. If this happens during OTA-related odex checks (ShareOatUtil), disable odex-compatible patching or align patching with the OTA state via isSystemOTA checks.
  4. Report the exact file path from the log and inspect it with readelf on a pulled copy to confirm truncation.

Example fix

// before
int bytesRead = channel.read(bufferOut);
// after (loop until limit or EOF, then fail with the same message)
int bytesRead = 0;
while (bytesRead < bufferOut.limit()) {
    int n = channel.read(bufferOut);
    if (n < 0) break;
    bytesRead += n;
}
Defensive patterns

Strategy: validation

Validate before calling

// Check file is long enough before ELF/OAT parsing
long need = headerSizeEstimate; // e.g. 64 for ELF64 ehdr
if (!file.exists() || file.length() < need) {
    throw new IOException("file too short: " + file);
}

Try / catch

try {
    elfFile = new ShareElfFile(file);
} catch (IOException e) {
    // truncated or unreadable ELF: delete artifact so it regenerates, skip oat logic
}

Prevention

When it happens

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

Common situations: 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.

Related errors


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