oracle/graal · error · IllegalMemoryAccessException

Memory access is outside the boundaries of the allocated mem

Error message

Memory access is outside the boundaries of the allocated memory region

What it means

Thrown by Espresso's ChunkedNativeMemory when a guest memory access (get/put of byte/short/int/long etc.) falls outside the bounds of the allocated chunk. The check is 'byteIndex < 0 || byteIndex > length - accessByteSize', so both negative offsets and offsets that would make a multi-byte access cross the end of the region are rejected. This is Espresso's equivalent of the JVM's AIOOBE for native/foreign memory, mapped to a guest IndexOutOfBoundsException-style error.

Source

Thrown at espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/ffi/memory/ChunkedNativeMemory.java:302

        validateAccess(chunk, chunkOffset + bytes, 0);
        return wrapChunk(chunk, chunkOffset, bytes);
    }

    /**
     * Validates the memory access and as a side effect enforces int lengths for byte-array chunks
     * by using {@link ChunkedNativeMemory#getChunkSize(Object)}.
     *
     * @param chunk the chunk that is accessed.
     * @param byteIndex the biggest index accessed in the chunk.
     * @param accessByteSize the byte size of the access.
     */
    protected void validateAccess(T chunk, long byteIndex, int accessByteSize) throws IllegalMemoryAccessException {
        validateAccess(getChunkSize(chunk), byteIndex, accessByteSize);
    }

    protected void validateAccess(long length, long byteIndex, int accessByteSize) throws IllegalMemoryAccessException {
        if (byteIndex < 0 || byteIndex > length - accessByteSize) {
            throw new IllegalMemoryAccessException("Memory access is outside the boundaries of the allocated memory region");
        }
    }

    protected abstract void putByteImpl(T chunk, long chunkOffset, byte value, MemoryAccessMode accessMode);

    protected abstract void putShortImpl(T chunk, long chunkOffset, short value, MemoryAccessMode accessMode);

    protected abstract void putIntImpl(T chunk, long chunkOffset, int value, MemoryAccessMode accessMode);

    protected abstract void putLongImpl(T chunk, long chunkOffset, long value, MemoryAccessMode accessMode);

    protected abstract byte getByteImpl(T chunk, long chunkOffset, MemoryAccessMode accessMode);

    protected abstract short getShortImpl(T chunk, long chunkOffset, MemoryAccessMode accessMode);

    protected abstract int getIntImpl(T chunk, long chunkOffset, MemoryAccessMode accessMode);

    protected abstract long getLongImpl(T chunk, long chunkOffset, MemoryAccessMode accessMode);

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Check byteIndex >= 0 && byteIndex + accessByteSize <= region size before the access (use getChunkSize(chunk) or the tracked length).
  2. Audit loops over native memory for off-by-one bounds (<= vs <) and for the width of the widest access (long = 8 bytes).
  3. If the offset comes from guest computation, validate/overflow-check it at the boundary before converting to a chunk offset.
  4. If the chunk was reallocated or freed, refresh the chunk reference and re-read its size.

Example fix

// before
memory.getInt(chunk, offset, MemoryAccessMode.PLAIN);

// after
if (offset < 0 || offset > ChunkedNativeMemory.getChunkSize(chunk) - 4) {
    throw new IndexOutOfBoundsException("offset " + offset + " out of bounds");
}
memory.getInt(chunk, offset, MemoryAccessMode.PLAIN);
Defensive patterns

Strategy: validation

Validate before calling

// before any chunk access: byteIndex in [0, size - accessByteSize]
long size = ChunkedNativeMemory.getChunkSize(chunk);
if (byteIndex < 0 || byteIndex > size - accessByteSize) {
    throw new IndexOutOfBoundsException("offset=" + byteIndex + " size=" + size + " access=" + accessByteSize);
}

Try / catch

try {
    memory.getInt(chunk, byteIndex, MemoryAccessMode.PLAIN);
} catch (IllegalMemoryAccessException e) {
    // bounds are caller-controlled: report, do not retry with the same offset
    throw new IndexOutOfBoundsException(e.getMessage());
}

Prevention

When it happens

Trigger: Calling putByte/putInt/getLong (or any accessor on ChunkedNativeMemory) with an index+accessSize that exceeds getChunkSize(chunk); guest code indexing past the end of a direct ByteBuffer-backed chunk; computing an offset with int overflow that wraps negative; accessing a freed/reallocated chunk whose size shrank.

Common situations: Guest Java code doing manual pointer arithmetic over a native buffer, off-by-one loops over array-like native memory, using a stale chunk reference after reallocation, or truncation bugs where a long offset is cast to int before the call.

Related errors


AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14). Data as JSON: /api/errors/aeada71d67e3b4a8. Report an issue: GitHub.