oracle/graal · error · ArrayIndexOutOfBoundsException

arrayOffset is beyond array length

Error message

arrayOffset is beyond array length

What it means

The upper-bound arm of the same Unsafe boundsCheck: after alignment, if offset > maxIndex - accessSize the access would read past the array's usable memory (including the alignment slack allowed for sub-word CAS). IllegalArrayAccessException('arrayOffset is beyond array length') is thrown.

Source

Thrown at espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/standard/Target_sun_misc_Unsafe.java:580

     * @throws IllegalArrayAccessException if the access is out of bounds
     */
    private static void boundsCheck(
                    @JavaType(Object.class) StaticObject o, long offset, long accessSize, EspressoLanguage language) throws IllegalArrayAccessException {
        // offset = baseOffset + index * indexScale
        assert o.getKlass().isArray();
        Klass klass = o.getKlass();
        int baseOffset = arrayBaseOffset(klass);
        int indexScale = arrayIndexScale(klass);
        if (offset < baseOffset) {
            throw new IllegalArrayAccessException("arrayOffset is less than baseOffset");
        }
        /*
         * Ensure memory is aligned for operations like sub-word CAS that may temporarily access
         * memory just beyond array bounds.
         */
        int maxIndex = alignUpToIntBytes(baseOffset + o.length(language) * indexScale);
        if (offset > maxIndex - accessSize) {
            throw new IllegalArrayAccessException("arrayOffset is beyond array length");
        }
    }

    /**
     * Thrown when {@link #boundsCheck} fails due to an out-of-bounds access.
     */
    private static class IllegalArrayAccessException extends Exception {
        @Serial private static final long serialVersionUID = 1L;

        IllegalArrayAccessException(String msg) {
            super(msg);
        }

        @SuppressWarnings("sync-override")
        @Override
        public final Throwable fillInStackTrace() {
            return this;
        }

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Clamp indices: ensure index*indexScale + accessSize <= array length * indexScale before the Unsafe call.
  2. Use bulk APIs (arraycopy, ByteBuffer) instead of per-element Unsafe access where possible.
  3. Audit hardcoded scale constants against Unsafe.arrayIndexScale for the actual element type.

Example fix

// before
unsafe.getLong(arr, base + i * 8); // i can be arr.length -> overrun

// after
if (i >= 0 && i < arr.length) {
    unsafe.getLong(arr, base + i * 8L);
}
Defensive patterns

Strategy: validation

Validate before calling

long base = unsafe.arrayBaseOffset(arr.getClass());
long scale = unsafe.arrayIndexScale(arr.getClass());
if (offset + accessSize > base + (long) arr.length * scale) throw new IndexOutOfBoundsException();

Prevention

When it happens

Trigger: Guest code using Unsafe with an index >= array.length, an accessSize that overruns the last element (e.g. getLong at the last int slot), or stale offsets after an array shrank/was reallocated.

Common situations: Serialization/copy libraries (Kryo-style) doing manual memory copies with Unsafe; off-by-one index math; race where another thread replaced the array between offset computation and access.

Related errors


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