Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invoking Class.forName through old reflection failed with ClassNotFound exception #20155

Closed
r30shah opened this issue Sep 11, 2024 · 6 comments · Fixed by ibmruntimes/openj9-openjdk-jdk21#195

Comments

@r30shah
Copy link
Contributor

r30shah commented Sep 11, 2024

I am looking into running sanity.openjdk tests with setting -Djdk.reflect.useDirectMethodHandle=false to switch to old core reflection for method.invoke calls instead of Method Handles. While testing the SDK with that option, it fails test in [1]. I am pasting here simplified version of the test here that reproduces the problem.

$ cat Test.java
import java.lang.invoke.*;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.lang.reflect.InvocationTargetException;

public class Test {
    static final String NAME = Test.class.getName();

    public static void main(String[] args) throws Throwable {
	final Method fnMethod = Class.class.getMethod("forName", String.class);
  	final MethodType mt = MethodType.methodType(Object.class, Object.class, Object[].class);

  	final MethodHandle mh = MethodHandles.lookup().bind(fnMethod, "invoke", mt);
	//mh.bindTo(null).bindTo(new Object[]{NAME}).invoke();
	//mh.invoke(null, new Object[]{NAME});
      	try {
  		mh.invokeWithArguments(null, NAME);
      	} catch (InvocationTargetException ex) {
      		ex.getCause().printStackTrace();
      	}
  	//mh.invokeWithArguments(Arrays.asList(null, NAME));
	//Object obj = mh.invokeExact((Object) null, new Object[]{NAME});
    }
}


$ java -Xshareclasses:none -Xint -Djdk.reflect.useDirectMethodHandle=false  Test
java.lang.ClassNotFoundException: Test
	at java.base/java.lang.Class.forNameImpl(Native Method)
	at java.base/java.lang.Class.forName(Class.java:372)
	at java.base/java.lang.Class.forName(Class.java:350)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:75)
	at java.base/jdk.internal.reflect.MethodAccessorImpl.invoke(MethodAccessorImpl.java:51)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:59)
	at java.base/java.lang.reflect.Method.invoke(Method.java:613)
	at java.base/java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:733)
	at java.base/java.lang.invoke.MethodHandleImpl$AsVarargsCollector.invokeWithArguments(MethodHandleImpl.java:544)
	at Test.main(Test.java:17)

I printed bunch of statements in Class.forName to see why it fails with ClassNotFoundException.
What I see that in the failing case when it calls the Class.forName with the caller class at [2], it considers java.lang.invoke.MethodHandleImpl$AsVarargsCollector.invokeWithArguments as the caller class and as that class is being loaded by system class loader, it tries to find Test class in system class loader which did not load Test class hence it fails with ClassNotFoundException. As ways to invoke the Class.forName works in [1], I looked into the difference, also checked out the getCallerClassJEP176Iterator, I see that when we iterate the stack frames, if we reach the depth 0 and the class at that depth is one of the reflection classes or method handle.invoke classes, it keeps iterating the stack.

I see that we do not check for MethodHandleImpl$AsVarargsCollector.invokeWithArguments causing us to think that the caller is that method.

I am opening up to discuss this failure and potential fix.

[1]. https://github.com/ibmruntimes/openj9-openjdk-jdk21/blob/openj9/test/jdk/java/lang/invoke/7196190/ClassForNameTest.java
[2].

return forName(className, getStackClass(1));

@r30shah
Copy link
Contributor Author

r30shah commented Sep 11, 2024

I added change in OpenJ9 to also check if the method is AsAVarargsCollector.invoke and added it to places where we also give special treatments to MethodHandle.invoke methods in [1].

@babsingh / @TobiAjila I am testing the fix in [1] to make sure that it passes the functional tests but given that this is in the VM side, would appreciate if I can get your POV on the failure as well.

[1]. r30shah@ea51632

@gacholio
Copy link
Contributor

Can we do this with the caller sensitive annotation instead?

@gacholio
Copy link
Contributor

gacholio commented Sep 12, 2024

I think I mean the frame iterator skip annotation.

@babsingh
Copy link
Contributor

babsingh commented Sep 12, 2024

  • In JDK17+, it will be the @Hidden annotation since OJDK MHs are enabled. It should fix the above error.
  • The error also persists in the RI. Using the @Hidden annotation should also work for the RI.

Note: -Djdk.reflect.useDirectMethodHandle=false was introduced in JDK18+ as part of JEP416, and it is no longer supported in JDK22+: https://bugs.openjdk.org/browse/JDK-8309635. -Djdk.reflect.useDirectMethodHandle=false is only supported between JDK18 to JDK21. So, the fix will only go in JDK21 since we no longer support JDKs 18-20.

babsingh added a commit to babsingh/openj9-openjdk-jdk21 that referenced this issue Sep 12, 2024
See eclipse-openj9/openj9#20155 for more
details on the failure and the fix.

Fixes: eclipse-openj9/openj9#20155

Signed-off-by: Babneet Singh <sbabneet@ca.ibm.com>
babsingh added a commit to babsingh/openj9-openjdk-jdk21 that referenced this issue Sep 12, 2024
See eclipse-openj9/openj9#20155 for more
details on the failure and the fix.

Fixes: eclipse-openj9/openj9#20155

Signed-off-by: Babneet Singh <sbabneet@ca.ibm.com>
@keithc-ca
Copy link
Contributor

Do we need similar changes for newer (or any other) versions, e.g. https://github.com/ibmruntimes/openj9-openjdk-jdk23 and https://github.com/ibmruntimes/openj9-openjdk-jdk?

@keithc-ca
Copy link
Contributor

Do we need similar changes for newer (or any other) versions?

As #20155 (comment) notes, jdk.reflect.useDirectMethodHandle is only relevant for jdk21.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants