{"record":{"id":"992f7aad1de7343d","repo":"oracle/graal","slug":"bad-receiver-expected-object-got","errorCode":null,"errorMessage":"Bad receiver: expected Object, got {}","messagePattern":"Bad receiver: expected Object, got (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"espresso-compiler-stub/src/com.oracle.truffle.espresso.vmaccess/src/com/oracle/truffle/espresso/vmaccess/EspressoExternalResolvedJavaMethod.java","lineNumber":307,"sourceCode":"            throw new NullPointerException(\"For instance methods, the receiver argument must not be null\");\n        } else if (receiver.isNull()) {\n            throw new IllegalArgumentException(\"For instance methods, the receiver argument must not represent a null constant\");\n        }\n        AbstractEspressoSignature signature = getSignature();\n        int parameterCount = signature.getParameterCount(false);\n        if (parameterCount != arguments.length) {\n            throw new IllegalArgumentException(\"Expected \" + parameterCount + \" arguments, got \" + arguments.length);\n        }\n        Object[] args = new Object[arguments.length + (isConstructor() || !isStatic() ? 1 : 0)];\n        int outputArgumentOffset = 0;\n        EspressoExternalVMAccess access = getAccess();\n        if (isConstructor()) {\n            EspressoExternalResolvedInstanceType type = (EspressoExternalResolvedInstanceType) getDeclaringClass();\n            args[0] = access.unsafeAllocateInstance(type).getValue();\n            outputArgumentOffset = 1;\n        } else if (!isStatic()) {\n            if (!(receiver instanceof EspressoExternalObjectConstant objectConstant)) {\n                throw new IllegalArgumentException(\"Bad receiver: expected Object, got \" + arguments[0].getJavaKind());\n            }\n            args[0] = objectConstant.getValue();\n            outputArgumentOffset = 1;\n        }\n        for (int i = 0; i < parameterCount; i++) {\n            JavaConstant argument = arguments[i];\n            JavaKind argumentKind = argument.getJavaKind();\n            /*\n             * Perform widening primitive conversions (JLS 5.1.2) in order to implement strict\n             * method invocation conversions (JLS 5.3). Also promote to stack kind.\n             */\n            args[i + outputArgumentOffset] = switch (signature.getParameterKind(i)) {\n                case Boolean -> switch (argumentKind) {\n                    case Boolean -> argument.asBoolean() ? 1 : 0;\n                    default ->\n                        throw new IllegalArgumentException(\"Bad argument kind at index \" + i + \": expected Boolean, got \" + argumentKind);\n                };\n                case Byte -> switch (argumentKind) {","sourceCodeStart":289,"sourceCodeEnd":325,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/espresso-compiler-stub/src/com.oracle.truffle.espresso.vmaccess/src/com/oracle/truffle/espresso/vmaccess/EspressoExternalResolvedJavaMethod.java#L289-L325","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Obtain the receiver via the same EspressoExternalVMAccess (constant reflection or snippet reflection) so it is an EspressoExternalObjectConstant","Verify with instanceof before invoke and fail with a clear message naming the producer","Add an assertion in test builds that every constant crossing into invoke comes from the Espresso access"],"exampleFix":"// before\nmethod.invoke(hostProvider.forObject(obj), args);\n\n// after\nJavaConstant receiver = espressoAccess.getConstantReflection().forObject(guestValue);\nassert !(receiver instanceof EspressoExternalObjectConstant eoc) || eoc.getValue().getMetaObject() != null;\nmethod.invoke(receiver, args);","handlingStrategy":"type-guard","validationCode":"if (!method.isStatic() && !method.isConstructor() && !(receiver instanceof EspressoExternalObjectConstant)) {\n    throw new IllegalStateException(\"receiver from wrong JVMCI backend: \" + receiver.getClass().getName());\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Note the message misreports the receiver problem as arguments[0].getJavaKind() — trust the guard, not the text","Keep one constant-reflection provider per pipeline; never cache constants across backends","Add provenance assertions on receivers in test builds to catch mixing early"],"tags":["espresso","jvmci","method-invocation","receiver-contract","type-mismatch"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}