manifold-systems / manifold

Manifold is a Java compiler plugin, its features include Metaprogramming, Properties, Extension Methods, Operator Overloading, Templates, a Preprocessor, and more.
http://manifold.systems/
Apache License 2.0
2.43k stars 125 forks source link

ReflectUtil.method(Class, String, Class...) Returning null, Module `jdk.unsupported` Not Loaded #460

Closed gudenau closed 1 year ago

gudenau commented 1 year ago

Describe the bug During runtime of a project that uses Manifold a NPE is thrown from internal Manifold code that is injected into my project's classes. Making a module depend on jdk.unsupported or adding the VM options --add-module jdk.unsupported causes the NPE to go away.

To Reproduce Steps to reproduce the behavior:

  1. Create project with jigsaw modules
  2. Run project with a module path
  3. NPE stemming from NecessaryEvilUtil failing to load s.m.Unsafe

Expected behavior The Manifold runtime should not be throwing a NPE

Screenshots If applicable, add screenshots to help explain your problem (drag/drop them here).

Desktop (please complete the following information):

Additional context I'm wondering if providing a real module-info via multi-release jars is required on newer Java versions. It may also be a good idea to modify NecessaryEvilUtil.getUnsafe to propagate the exception that is caught.

Stack trace

java.lang.RuntimeException: The 'Unsafe' class is not accessible
    at manifold.util@2023.1.10/manifold.util.NecessaryEvilUtil.getUnsafe(NecessaryEvilUtil.java:48)
    at manifold.util@2023.1.10/manifold.util.ReflectUtil.setAccessible(ReflectUtil.java:911)
    at manifold.util@2023.1.10/manifold.util.ReflectUtil.setAccessible(ReflectUtil.java:891)
    at manifold.util@2023.1.10/manifold.util.ReflectUtil.lambda$static$2(ReflectUtil.java:88)
    at manifold.util@2023.1.10/manifold.util.concurrent.LocklessLazyVar$1.init(LocklessLazyVar.java:91)
    at manifold.util@2023.1.10/manifold.util.concurrent.LocklessLazyVar.get(LocklessLazyVar.java:40)
    at manifold.util@2023.1.10/manifold.util.ReflectUtil.getDeclaredMethod(ReflectUtil.java:394)
    at manifold.util@2023.1.10/manifold.util.ReflectUtil._method(ReflectUtil.java:335)
    at manifold.util@2023.1.10/manifold.util.ReflectUtil.method(ReflectUtil.java:317)
    at manifold.util@2023.1.10/manifold.util.JreUtil.isJava9Modular_runtime(JreUtil.java:183)
    at manifold.rt@2023.1.10/manifold.rt.api.util.ServiceUtil.hackServiceLoaderToHandleProxyFactoryForJpms(ServiceUtil.java:99)
    at manifold.rt@2023.1.10/manifold.rt.api.util.ServiceUtil.loadRegisteredServices(ServiceUtil.java:41)
    at manifold.rt@2023.1.10/manifold.rt.api.Bootstraps.lambda$static$0(Bootstraps.java:27)
    at manifold.util@2023.1.10/manifold.util.concurrent.LocklessLazyVar$1.init(LocklessLazyVar.java:91)
    at manifold.util@2023.1.10/manifold.util.concurrent.LocklessLazyVar.get(LocklessLazyVar.java:40)
    at manifold.rt@2023.1.10/manifold.rt.api.Bootstraps.get(Bootstraps.java:32)
    at manifold.rt@2023.1.10/manifold.rt.api.IBootstrap.dasBoot(IBootstrap.java:31)
    at project.main/net.gudenau.project.Project.<clinit>(Project.java:1)
java.lang.NoClassDefFoundError: sun/misc/Unsafe
    at manifold.util@2023.1.10/manifold.util.NecessaryEvilUtil.getUnsafe(NecessaryEvilUtil.java:42)
    at manifold.util@2023.1.10/manifold.util.ReflectUtil.setAccessible(ReflectUtil.java:911)
    at manifold.util@2023.1.10/manifold.util.ReflectUtil.setAccessible(ReflectUtil.java:891)
    at manifold.util@2023.1.10/manifold.util.ReflectUtil.lambda$static$2(ReflectUtil.java:88)
    at manifold.util@2023.1.10/manifold.util.concurrent.LocklessLazyVar$1.init(LocklessLazyVar.java:91)
    at manifold.util@2023.1.10/manifold.util.concurrent.LocklessLazyVar.get(LocklessLazyVar.java:40)
    at manifold.util@2023.1.10/manifold.util.ReflectUtil.getDeclaredMethod(ReflectUtil.java:394)
    at manifold.util@2023.1.10/manifold.util.ReflectUtil._method(ReflectUtil.java:335)
    at manifold.util@2023.1.10/manifold.util.ReflectUtil.method(ReflectUtil.java:317)
    at manifold.util@2023.1.10/manifold.util.JreUtil.isJava9Modular_runtime(JreUtil.java:183)
    at manifold.rt@2023.1.10/manifold.rt.api.util.ServiceUtil.hackServiceLoaderToHandleProxyFactoryForJpms(ServiceUtil.java:99)
    at manifold.rt@2023.1.10/manifold.rt.api.util.ServiceUtil.loadRegisteredServices(ServiceUtil.java:41)
    at manifold.rt@2023.1.10/manifold.rt.api.Bootstraps.lambda$static$0(Bootstraps.java:27)
    at manifold.util@2023.1.10/manifold.util.concurrent.LocklessLazyVar$1.init(LocklessLazyVar.java:91)
    at manifold.util@2023.1.10/manifold.util.concurrent.LocklessLazyVar.get(LocklessLazyVar.java:40)
    at manifold.rt@2023.1.10/manifold.rt.api.Bootstraps.get(Bootstraps.java:32)
    at manifold.rt@2023.1.10/manifold.rt.api.IBootstrap.dasBoot(IBootstrap.java:31)
    at project.main/net.gudenau.project.Project.<clinit>(Project.java:1)
Caused by: java.lang.ClassNotFoundException: sun.misc.Unsafe
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
    ... 18 more
rsmckinney commented 1 year ago

@gudenau. Yes, when developing with named JPMS modules the sun.misc.Unsafe class is not accessible without requires jdk.unsupported in your module-info. This is the unfortunate nature of JPMS runtime "security". The manifold sample project provides a module-info for reference. It demonstrates common requires statements for use with various manifold libraries, including jdk.unsupported.

module manifold.sample.project {
  requires manifold.util;
  requires manifold.rt;
  requires manifold.ext.rt;
  requires manifold.props.rt;
  requires manifold.delegation.rt;
  requires manifold.tuple.rt;
  requires manifold.graphql.rt;
  requires manifold.json.rt;
  requires manifold.xml.rt;
  requires manifold.yaml.rt;
  requires manifold.csv.rt;
  requires manifold.js.rt;
  requires manifold.templates.rt;
  requires manifold.science;
  requires manifold.collections;

  requires java.desktop;
  requires jdk.unsupported;

  // Register the sample Date proxy factory service implementation
  // (note the META-INF/services registration is still necessary for Java 8 and Java 9+ unnamed-module)
  provides manifold.ext.rt.api.IProxyFactory
    with Date_To_ChronoLocalDateTime;
}
gudenau commented 1 year ago

LWJGL3 makes use of Unsafe and has a module-info viamulti release jars, which completely prevents this issue.

rsmckinney commented 1 year ago

Can you elaborate?

gudenau commented 1 year ago

Multi-release jars are a Java 9 feature that allows a single jar file to have code for multiple versions of the Java runtime.

You set the attribute Multi-Release to true in the manifest and put classes for different Java versions in META-INF/versions/n, where n is the major Java version starting with 9. Since Java 8 and earlier don't support this they completely ignore this directory and manifest attribute.

Let's say you have a multi-release jar file that looks something like this:

META-INF/
- MANIFEST.MF
- versions/
- - 9/example/
- - - Example.class
- - 10/example/
- - - Example.class
example/
- Example.class

Where the root example.Example contains the following:

package example;

public final class Example {
    public static void main(String[] args) {
        System.out.println("Java 8");
    }
}

and the Java 9 example.Example contains the following:

package example;

public final class Example {
    public static void main(String[] args) {
        System.out.println("Java " + Runtime.version().major());
    }
}

and finally the Java 10+ example.Example contains:

package example;

public final class Example {
    public static void main(String[] args) {
        System.out.println("Java " + Runtime.version().feature());
    }
}

On Java 8 the class in the root will be used automaticly, on Java 9 the one in versions/9 will be used and in Java 10+ the one in versions/10 will be used. There are a few rules to be mindful of, but for the purposes of adding a module-info file they don't particularly matter.

With the LWJGL3 example they have the module-info file located at META-INF/versions/9/module-info.class, they also use this to enable faster code paths enabled by newer Java releases without having branches or using reflection.

rsmckinney commented 1 year ago

Thanks. I’m aware of multi release jars. Are you suggesting manifold would not need Unsafe with this feature? If so, that’s not really the case. The Java.base and jdk modules still need to be opened dynamically etc. for other purposes. And Unsafe is still used for other stuff. And for manifold, multi release jars feature doesn’t really work out well. I’d use it if it worked for my use case. I appreciate the suggestion though, thanks.