oracle/graal · error · IllegalArgumentException

No ClassConstant at constant pool index ${cpi}

Error message

No ClassConstant at constant pool index ${cpi}

What it means

IllegalArgumentException from Klass.resolveClassConstantInPool: when resolving constant pool entry cpi as a CONSTANT_Class, the entry at that index is not a class constant (ClassCastException) or the index is outside the pool (IndexOutOfBoundsException). Both are converted to 'No ClassConstant at constant pool index <cpi>'.

Source

Thrown at espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Klass.java:1916

    }

    @Override
    public final boolean isMagicAccessor() {
        if (getJavaVersion().java23OrEarlier()) {
            assert getMeta().sun_reflect_MagicAccessorImpl != null;
            return getMeta().sun_reflect_MagicAccessorImpl.isAssignableFrom(this);
        }
        return false;
    }

    @Override
    @TruffleBoundary
    public final Klass resolveClassConstantInPool(int cpi) {
        if (this instanceof ObjectKlass objectKlass) {
            try {
                return objectKlass.getConstantPool().resolvedKlassAt(objectKlass, cpi, false);
            } catch (ClassCastException | IndexOutOfBoundsException e) {
                throw new IllegalArgumentException("No ClassConstant at constant pool index " + cpi);
            }
        }
        return null;
    }

    @Override
    public Method lookupVTableEntry(int vtableIndex) {
        ObjectKlass k;
        if (this instanceof ObjectKlass) {
            k = (ObjectKlass) this;
        } else if (this instanceof ArrayKlass) {
            k = getMeta().java_lang_Object;
        } else {
            // primitive.
            return null;
        }
        assert !k.isInterface();
        Method.MethodVersion[] table = k.getVTable();

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Validate the tag at cpi is CONSTANT_Class before resolving (Espresso's pool API exposes tag lookup).
  2. Re-derive indices from the same class bytes that will be resolved — never cache cp indices across recompiles.
  3. Bounds-check cpi against getConstantPool().length().
  4. Catch IllegalArgumentException at the caller and fall back to a non-constant-pool resolution path if the index is untrusted.

Example fix

// before
Klass k = klass.resolveClassConstantInPool(cpi);

// after
if (cpi < 0 || cpi >= objectKlass.getConstantPool().length() ||
                objectKlass.getConstantPool().tagAt(cpi) != ConstantTag.CLASS) {
    throw new IllegalArgumentException("cpi " + cpi + " is not a class constant");
}
Klass k = klass.resolveClassConstantInPool(cpi);
Defensive patterns

Strategy: validation

Validate before calling

ConstantPool pool = objectKlass.getConstantPool();
if (cpi < 0 || cpi >= pool.length() || pool.tagAt(cpi) != ConstantTag.CLASS) {
    throw new IllegalArgumentException("cpi " + cpi + " is not a CONSTANT_Class entry");
}

Try / catch

try {
    return klass.resolveClassConstantInPool(cpi);
} catch (IllegalArgumentException e) {
    // index not a class constant: re-derive cpi from current class bytes or fail gracefully
}

Prevention

When it happens

Trigger: JVMCI/reflection-style API (resolveClassConstantInPool) invoked with a cpi that points to a String, MethodType, or other non-Class entry, or an index beyond pool length; bytecode instrumentation rewriting a class entry into a different tag without updating the caller.

Common situations: Tooling built on Espresso's Klass API (debuggers, hotswap, AOT scanners) that reads cp indices from one class version while the pool belongs to another; recompilation changing constant pool layout between the instrumentation pass and resolution.

Related errors


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