{"record":{"id":"2f12053b8f2310e8","repo":"oracle/graal","slug":"for-static-methods-or-constructors-the-receiver-a","errorCode":null,"errorMessage":"For static methods or constructors, the receiver argument must be null","messagePattern":"For static methods or constructors, the receiver argument must be null","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":286,"sourceCode":"    protected boolean equals0(AbstractEspressoResolvedJavaMethod that) {\n        if (that instanceof EspressoExternalResolvedJavaMethod espressoMethod) {\n            return this.vmMethodMirror.equals(espressoMethod.vmMethodMirror);\n        }\n        return false;\n    }\n\n    @Override\n    protected int hashCode0() {\n        return vmMethodMirror.hashCode();\n    }\n\n    /// Adapts a call to a guest Espresso method, converting between [JavaConstant] values and\n    /// the polyglot [Value]s, and back. The interop implementation in the Espresso is\n    /// `com.oracle.truffle.espresso.impl.Method.Execute#invoke`.\n    JavaConstant invoke(JavaConstant receiver, JavaConstant... arguments) {\n        if (isStatic() || isConstructor()) {\n            if (receiver != null) {\n                throw new IllegalArgumentException(\"For static methods or constructors, the receiver argument must be null\");\n            }\n        } else if (receiver == null) {\n            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;","sourceCodeStart":268,"sourceCodeEnd":304,"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#L268-L304","documentation":"Thrown by EspressoExternalResolvedJavaMethod.invoke when invoking a static method or constructor with a non-null receiver. Constructors get their receiver (the allocated instance) from unsafeAllocateInstance internally, and static methods have no receiver at all, so any receiver constant supplied by the caller makes the argument-to-Value marshalling ambiguous and is rejected up front. This is a usage-contract guard on the private invoke adapter used by the external vmaccess layer to call into the guest Espresso VM.","triggerScenarios":"Calling invoke(methodIsStatic, receiver, args...) with a receiver constant because the call site handles static and instance methods uniformly; adapting ResolvedJavaMethod.invokeStatic/invokeWithArguments generically without branching on isStatic()/isConstructor().","commonSituations":"Generic reflection-style bridges (snippets, substitution frameworks, JVMTI-like tooling) that pass the receiver through for all methods; refactors that changed a method from instance to static while callers kept passing 'this'.","solutions":["Branch on method.isStatic() || method.isConstructor() and pass null as the receiver in those cases","For constructors, pass only the declared parameters — the adapter allocates the instance itself","Wrap call sites in a helper that normalizes (receiver, args) according to the method modifiers"],"exampleFix":"// before\nJavaConstant result = method.invoke(receiverMaybeNonNull, args);\n\n// after\nJavaConstant result = method.invoke(\n    method.isStatic() || method.isConstructor() ? null : receiverMaybeNonNull,\n    args);","handlingStrategy":"validation","validationCode":"boolean noReceiver = method.isStatic() || method.isConstructor();\nif (noReceiver && receiver != null) throw new IllegalStateException(\"drop receiver for \" + method.getName());\nJavaConstant r = noReceiver ? null : requireNonNull(receiver);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Route all calls through one helper that normalizes receiver nullability from method.isStatic()/isConstructor()","Constructors: the adapter allocates the instance itself — pass only declared parameters","When a method changes from instance to static, update call sites, not just the resolution"],"tags":["espresso","jvmci","method-invocation","receiver-contract","static-method"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}