oracle/graal · error · IllegalArgumentException

Bad receiver: expected Object, got {}

Error message

Bad receiver: expected Object, got {}

What it means

Thrown by EspressoExternalResolvedJavaMethod.invoke when an instance method's receiver JavaConstant is not an EspressoExternalObjectConstant. Like the field-write guard, the adapter needs the guest polyglot Value inside EspressoExternalObjectConstant to place at args[0] for the interop execute; constants from other JVMCI backends cannot be executed on. The message text prints arguments[0].getJavaKind(), which is misleading (a copy-paste from the argument loop) — the real problem is the receiver constant's provenance.

Source

Thrown at espresso-compiler-stub/src/com.oracle.truffle.espresso.vmaccess/src/com/oracle/truffle/espresso/vmaccess/EspressoExternalResolvedJavaMethod.java:307

            throw new NullPointerException("For instance methods, the receiver argument must not be null");
        } else if (receiver.isNull()) {
            throw new IllegalArgumentException("For instance methods, the receiver argument must not represent a null constant");
        }
        AbstractEspressoSignature signature = getSignature();
        int parameterCount = signature.getParameterCount(false);
        if (parameterCount != arguments.length) {
            throw new IllegalArgumentException("Expected " + parameterCount + " arguments, got " + arguments.length);
        }
        Object[] args = new Object[arguments.length + (isConstructor() || !isStatic() ? 1 : 0)];
        int outputArgumentOffset = 0;
        EspressoExternalVMAccess access = getAccess();
        if (isConstructor()) {
            EspressoExternalResolvedInstanceType type = (EspressoExternalResolvedInstanceType) getDeclaringClass();
            args[0] = access.unsafeAllocateInstance(type).getValue();
            outputArgumentOffset = 1;
        } else if (!isStatic()) {
            if (!(receiver instanceof EspressoExternalObjectConstant objectConstant)) {
                throw new IllegalArgumentException("Bad receiver: expected Object, got " + arguments[0].getJavaKind());
            }
            args[0] = objectConstant.getValue();
            outputArgumentOffset = 1;
        }
        for (int i = 0; i < parameterCount; i++) {
            JavaConstant argument = arguments[i];
            JavaKind argumentKind = argument.getJavaKind();
            /*
             * Perform widening primitive conversions (JLS 5.1.2) in order to implement strict
             * method invocation conversions (JLS 5.3). Also promote to stack kind.
             */
            args[i + outputArgumentOffset] = switch (signature.getParameterKind(i)) {
                case Boolean -> switch (argumentKind) {
                    case Boolean -> argument.asBoolean() ? 1 : 0;
                    default ->
                        throw new IllegalArgumentException("Bad argument kind at index " + i + ": expected Boolean, got " + argumentKind);
                };
                case Byte -> switch (argumentKind) {

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Obtain the receiver via the same EspressoExternalVMAccess (constant reflection or snippet reflection) so it is an EspressoExternalObjectConstant
  2. Verify with instanceof before invoke and fail with a clear message naming the producer
  3. Add an assertion in test builds that every constant crossing into invoke comes from the Espresso access

Example fix

// before
method.invoke(hostProvider.forObject(obj), args);

// after
JavaConstant receiver = espressoAccess.getConstantReflection().forObject(guestValue);
assert !(receiver instanceof EspressoExternalObjectConstant eoc) || eoc.getValue().getMetaObject() != null;
method.invoke(receiver, args);
Defensive patterns

Strategy: type-guard

Validate before calling

if (!method.isStatic() && !method.isConstructor() && !(receiver instanceof EspressoExternalObjectConstant)) {
    throw new IllegalStateException("receiver from wrong JVMCI backend: " + receiver.getClass().getName());
}

Prevention

When it happens

Trigger: Invoking an instance method with a receiver constant created by a host ConstantReflectionProvider, a mock JavaConstant, or another VM access instance. The bug is always at the call site that sourced the receiver.

Common situations: Shared compiler infrastructure running against both a host JVMCI and the Espresso external vmaccess, with receiver constants cached across backends; partial migrations to the espresso-compiler-stub where only some providers were swapped.

Related errors


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