JDK-8349716 : IllegalAccessError when Proxy methods return object of a package-private type
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang:reflect
  • Affected Version: 8,11,17,21,24
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: generic
  • CPU: generic
  • Submitted: 2025-02-10
  • Updated: 2025-04-13
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
CSR :  
Relates :  
Description
ADDITIONAL SYSTEM INFORMATION :
Microsoft Windows 10 Version 22H2 (OS Build 19045.5371)

java version "23.0.2" 2025-01-21
Java(TM) SE Runtime Environment (build 23.0.2+7-58)
Java HotSpot(TM) 64-Bit Server VM (build 23.0.2+7-58, mixed mode, sharing)

Also observed with OpenJDK Runtime Environment Temurin-21.0.5+11 (build 21.0.5+11-LTS)


A DESCRIPTION OF THE PROBLEM :
When creating a method handle for a annotation inside of a package visible annotation with a Lookup object that has access to the package, the creation of the MethodHandle succeeds, but calling MethodHandle,invoke fails with an IllegalAccessError.
This happens only when the annotation is "real" annotation uses a target of the invocation; if the interface is implemented by a class it works as expected.
This works as expected with OpenJ9

Developer (liach) note:
When creating a Proxy that has a method that returns objects of package-private types, and that particular method is called and returns non-null, the method throws an `IllegalAccessError`.
The culprit is the checkcast in codeUnwrapValue, which does not perform class or interface resolution when the value is `null`. This has been in place since the initial load of OpenJDK.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile and run the source code


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Code runs without exception
ACTUAL -
Exception in thread "main" java.lang.IllegalAccessError: failed to access class Reflection$Y from class jdk.proxy2.$Proxy1 (Reflection$Y is in unnamed module of loader 'app'
; jdk.proxy2.$Proxy1 is in module jdk.proxy2 of loader 'app')
        at jdk.proxy2/jdk.proxy2.$Proxy1.value(Unknown Source)
        at Reflection.main(Reflection.java:39)


---------- BEGIN SOURCE ----------
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.stream.Stream;

final class Reflection {

        @Reflection.Y.Y1({})
        private Reflection() {
        }

        @Retention(RetentionPolicy.RUNTIME)
        @interface Y {
                String value();

                @Retention(RetentionPolicy.RUNTIME)
                @interface Y1 {
                        Y[] value();
                }
        }

        public static void main(String... args) throws Throwable {
                MethodHandle methodHandle = MethodHandles.lookup().findVirtual(Reflection.Y.Y1.class, "value", MethodType.methodType(Y[].class));
                Reflection.Y.Y1 y1 = Reflection.class.getDeclaredConstructor().getAnnotation(Reflection.Y.Y1.class);

                methodHandle.invoke(y1);
        }
}

---------- END SOURCE ----------


Comments
A pull request was submitted for review. Branch: master URL: https://git.openjdk.org/jdk/pull/24611 Date: 2025-04-13 09:49:46 +0000
13-04-2025

Stopping work for now as it is a bit complex for the verification when the type is (an array type with component of) a class instead of an interface.
12-02-2025

A pull request was submitted for review. Branch: master URL: https://git.openjdk.org/jdk/pull/23567 Date: 2025-02-11 17:56:04 +0000
11-02-2025

When creating a Proxy that has a method that returns objects of package-private types, and that particular method is called and returns non-null, the method throws an `IllegalAccessError`. The culprit is the checkcast in codeUnwrapValue, which does not perform class or interface resolution when the value is `null`. This has been in place since the initial load of OpenJDK. A fix can be something like the check casts in InvokerBytecodeGenerator.emitReturn. Here's a reproducer slightly adjusted from that provided by guice: public class Z { public interface I { J f(); } interface J { void g(); } public static void main(String... args) { I proxy = (I) Proxy.newProxyInstance( I.class.getClassLoader(), new Class<?>[] {I.class}, (p, m, a) -> (J)()->{}); proxy.f(); } }
11-02-2025

[~cushon] It's interesting that the test coverage in guice that reported JDK-8333854 did not have a case where a package-private type is returned non-null, so this problem, which IMO is much more serious than 8333854, was never even noticed!
11-02-2025

The scenario is similar to that of JDK-8333854, where invoke has stricter access checks, and the problematic code also involves the appearance of package-private types in proxied interface methods.
11-02-2025

Observation on Windows 11 -------------------------------------- JDK 8u441 : Failed (java.lang.IllegalAccessError: tried to access class Reflection$Y from class com.sun.proxy.$Proxy1) JDK 11.0.25: Failed JDK 17.0.14 : Failed (java.lang.IllegalAccessError: failed to access class Reflection$Y from class jdk.proxy2.$Proxy1) JDK 21.0.4 : Failed JDK 24.0.1ea: Failed
10-02-2025