{"record":{"id":"e7e4f91b8444bbe8","repo":"oracle/graal","slug":"undecoded-bytes","errorCode":null,"errorMessage":" undecoded bytes","messagePattern":" undecoded bytes","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/OptionsEncoder.java","lineNumber":81,"sourceCode":"        }\n    }\n\n    /**\n     * Decodes {@code input} into a name/value map.\n     *\n     * @throws IllegalArgumentException if {@code input} cannot be decoded\n     */\n    public static Map<String, Object> decode(byte[] input) {\n        Map<String, Object> res = new LinkedHashMap<>();\n        try (TypedDataInputStream in = new TypedDataInputStream(new ByteArrayInputStream(input))) {\n            final int size = in.readInt();\n            for (int i = 0; i < size; i++) {\n                final String key = in.readUTF();\n                final Object value = in.readTypedValue();\n                res.put(key, value);\n            }\n            if (in.available() != 0) {\n                throw new IllegalArgumentException(in.available() + \" undecoded bytes\");\n            }\n        } catch (IOException ioe) {\n            throw new IllegalArgumentException(ioe);\n        }\n        return res;\n    }\n}\n","sourceCodeStart":63,"sourceCodeEnd":89,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/OptionsEncoder.java#L63-L89","documentation":"IllegalArgumentException from OptionsEncoder.decode when the input byte array decodes its declared count of key/value pairs but bytes still remain in the stream (in.available() != 0). This means the payload is not a self-consistent OptionsEncoder payload: it was truncated/corrupted, produced by a different writer, or is raw data that merely looks like encoded options.","triggerScenarios":"Calling OptionsEncoder.decode(input) where input was truncated, padded, concatenated with other data, or produced by a mismatched encoder (different type-tag scheme or version) — readInt consumed the size, the loop consumed size entries, and trailing bytes are left over.","commonSituations":"Reading encoded options embedded in a compiled artifact after a partial write or file corruption; version skew where the encoder wrote extra fields the decoder loop does not consume; feeding an arbitrary byte[] (e.g. from a different format) into decode by mistake.","solutions":["Verify provenance: re-encode the options on the same GraalVM build and compare lengths — a length mismatch confirms corruption or version skew.","If the payload came from storage/network, re-obtain or re-transfer it (it is not recoverable client-side).","Ensure encode and decode run on the same compiler version / same TypedDataInputStream tag set.","Wrap decode at the trust boundary and reject malformed payloads explicitly rather than letting the exception propagate mid-initialization."],"exampleFix":"// before\nMap<String,Object> opts = OptionsEncoder.decode(readBytesFromFile(f));\n\n// after: validate payload integrity before decoding\nbyte[] b = readBytesFromFile(f);\nif (b.length < 4) throw new IllegalArgumentException(\"payload too short\");\nMap<String,Object> opts = OptionsEncoder.decode(b);","handlingStrategy":"validation","validationCode":"// Cheap integrity check before decode: first 4 bytes declare entry count;\n// verify payload length is at least a minimal encoded body\nstatic boolean looksLikeEncodedOptions(byte[] b) {\n    if (b == null || b.length < 6) return false;\n    int count = ((b[0] & 0xFF) << 24) | ((b[1] & 0xFF) << 16) | ((b[2] & 0xFF) << 8) | (b[3] & 0xFF);\n    return count >= 0 && count * 4 <= b.length; // each entry needs tag+len at minimum\n}","typeGuard":null,"tryCatchPattern":"try {\n    return OptionsEncoder.decode(payload);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().endsWith(\"undecoded bytes\")) {\n        logCorruptPayload(payloadId); // re-fetch / discard artifact\n        return Map.of();\n    }\n    throw e;\n}","preventionTips":["Store a checksum (CRC32/SHA-256) alongside encoded option payloads and verify before decoding.","Write payloads atomically (temp file + rename) so partial writes never look like valid payloads.","Pin encoder/decoder to the same compiler version in mixed-version deployments."],"tags":["serialization","options","decoding","corruption","illegal-argument"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}