oracle/graal · error · ArrayIndexOutOfBoundsException

arrayOffset is less than baseOffset

Error message

arrayOffset is less than baseOffset

What it means

In the sun.misc.Unsafe substitution, boundsCheck validates a raw byte offset against an array's layout: offset = baseOffset + index * indexScale. If offset < arrayBaseOffset(klass), the computed index would be negative, and IllegalArrayAccessException('arrayOffset is less than baseOffset') is thrown instead of reading out of bounds.

Source

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

    /**
     * Checks if a memory access in a guest array is within bounds.
     *
     * @param o guest array object
     * @param offset raw byte offset into array
     * @param accessSize number of bytes to be accessed
     * @param language the EspressoLanguage instance
     * @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) {

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Recompute offsets with Unsafe.arrayBaseOffset(clazz) + index * Unsafe.arrayIndexScale(clazz) for the exact array class.
  2. Prefer VarHandle or java.nio buffers over raw Unsafe offsets.
  3. If a third-party library triggers it, check for an updated version compatible with Espresso's Unsafe semantics.

Example fix

// before
long offset = 8L * i; // hardcoded, below baseOffset
unsafe.getLong(arr, offset);

// after
long offset = ((long) unsafe.arrayBaseOffset(long[].class)) + i * unsafe.arrayIndexScale(long[].class);
unsafe.getLong(arr, offset);
Defensive patterns

Strategy: validation

Validate before calling

long base = unsafe.arrayBaseOffset(arr.getClass());
long scale = unsafe.arrayIndexScale(arr.getClass());
if (offset < base) throw new IllegalArgumentException("offset below base"); // fail before Unsafe call

Try / catch

try {
    value = unsafe.getLong(arr, offset);
} catch (Exception e) { // IllegalArrayAccessException surfaces as guest AIOOBE-equivalent
    // recompute offset with arrayBaseOffset/arrayIndexScale
}

Prevention

When it happens

Trigger: Guest code calling Unsafe.getXxx/putXxx/compareAndSwap on an array with an offset below the array's base offset (e.g. offset 0 or a value computed for a different element type), or misuse of Unsafe.arrayIndexScale math.

Common situations: Libraries using sun.misc.Unsafe with hand-computed offsets (off-heap style code pointed at Java arrays); offsets obtained from one array type applied to another; deliberately hostile/probing code.

Related errors


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