A truncated .beam is handled. A corrupt one takes down indexing.
Chunk.from reads a chunk's declared length as an unsigned 32-bit value into a Long, then narrows it with length.toInt() and hands it straight to ByteArray(count). That allocation sits outside the try that is supposed to make these reads safe, and nothing bounds the length first. Two shapes, both reproduced:
| Declared chunk length |
Narrowed Int |
Result |
0xFFFFFFFF |
-1 |
NegativeArraySizeException |
0x7FFFFFFF |
2147483647 |
OutOfMemoryError: Requested array size exceeds VM limit |
in between, e.g. 0x40000000 |
1073741824 |
allocates 1 GB, or fails — depends on the heap |
Neither escapes into a handler. NegativeArraySizeException is not an IOException, so none of the four catch blocks in Beam.from sees it. OutOfMemoryError is an Error, so even a catch (Exception) would miss it. No caller catches RuntimeException either.
Why this matters more than a decompiler glitch
Cache.from(FileContent) is a FileBasedIndexExtension input. So this is not a broken decompiler view that a user can close — a single corrupt .beam anywhere in a dependency fails indexing for the whole project, and nothing tells the user which file did it.
The contrast with the truncation path is the point. A .beam cut short mid-write degrades exactly as intended: reading stops, complete chunks are kept, nothing is logged. That tolerance was added in 80511d6 and has worked since v11.12.0. It just never extended to a length that is present but implausible.
Reproducing it
A twelve-byte input is enough — FOR1, any length, BEAM, then a chunk header of AtU8 and the length above. No real .beam needed, and the OutOfMemoryError case reproduced under a 4 GB test heap, so it is not a small-heap artefact. Note that the test JVM writes a ~365 MB heap dump when it fires.
Notes for whoever fixes it
- One expression, one file.
safeReadBytes has three call sites and the other two pass a literal 4. The narrowing in Chunk.from is the only place under beam/ where a length taken from the file reaches an allocation.
- A negative check is not sufficient, and neither is a new
catch — the middle row above is positive, and catching OutOfMemoryError would still mean having tried to allocate a nonsense array. What is needed is a plausibility bound on the length before the allocation, inside the try, so an implausible length degrades the same way a short read already does.
- The guard needs a mutation check. Neuter it once written and confirm the malformed cases actually go red. Passing tests against an unbounded allocation prove nothing.
SdkBeamParseTest covers 1666 real .beam files and is worth running, but none of them is corrupt, so it can only show the fix broke nothing — it cannot exercise the path being fixed.
Found while reviewing #1101, which reported the truncation message and is genuinely fixed. This is a different failure mode at a different severity, so it is filed on its own rather than reopening that one.
Filed as part of an AI-assisted review of the backlog. A human checked this one before it was filed, but it might still be wrong — if any of it is, please say so here.
A truncated
.beamis handled. A corrupt one takes down indexing.Chunk.fromreads a chunk's declared length as an unsigned 32-bit value into aLong, then narrows it withlength.toInt()and hands it straight toByteArray(count). That allocation sits outside thetrythat is supposed to make these reads safe, and nothing bounds the length first. Two shapes, both reproduced:Int0xFFFFFFFF-1NegativeArraySizeException0x7FFFFFFF2147483647OutOfMemoryError: Requested array size exceeds VM limit0x400000001073741824Neither escapes into a handler.
NegativeArraySizeExceptionis not anIOException, so none of the fourcatchblocks inBeam.fromsees it.OutOfMemoryErroris anError, so even acatch (Exception)would miss it. No caller catchesRuntimeExceptioneither.Why this matters more than a decompiler glitch
Cache.from(FileContent)is aFileBasedIndexExtensioninput. So this is not a broken decompiler view that a user can close — a single corrupt.beamanywhere in a dependency fails indexing for the whole project, and nothing tells the user which file did it.The contrast with the truncation path is the point. A
.beamcut short mid-write degrades exactly as intended: reading stops, complete chunks are kept, nothing is logged. That tolerance was added in 80511d6 and has worked since v11.12.0. It just never extended to a length that is present but implausible.Reproducing it
A twelve-byte input is enough —
FOR1, any length,BEAM, then a chunk header ofAtU8and the length above. No real.beamneeded, and theOutOfMemoryErrorcase reproduced under a 4 GB test heap, so it is not a small-heap artefact. Note that the test JVM writes a ~365 MB heap dump when it fires.Notes for whoever fixes it
safeReadByteshas three call sites and the other two pass a literal4. The narrowing inChunk.fromis the only place underbeam/where a length taken from the file reaches an allocation.catch— the middle row above is positive, and catchingOutOfMemoryErrorwould still mean having tried to allocate a nonsense array. What is needed is a plausibility bound on the length before the allocation, inside thetry, so an implausible length degrades the same way a short read already does.SdkBeamParseTestcovers 1666 real.beamfiles and is worth running, but none of them is corrupt, so it can only show the fix broke nothing — it cannot exercise the path being fixed.Found while reviewing #1101, which reported the truncation message and is genuinely fixed. This is a different failure mode at a different severity, so it is filed on its own rather than reopening that one.
Filed as part of an AI-assisted review of the backlog. A human checked this one before it was filed, but it might still be wrong — if any of it is, please say so here.