clj-reflector-graal-java11-fix
clj-reflector-graal-java11-fix copied to clipboard
A fix for an issue with clojure.lang.Reflector in GraalVM native-image JDK11.
clj-reflector-graal-java11-fix
:tada: Great news! Starting with GraalVM v21, this fix should no longer be needed.
Instead, you will probably need to add this to your reflection config:
[{"name": "java.lang.reflect.AccessibleObject",
"methods" : [{"name":"canAccess"}]},
...
]
This library offers a fix for an issue with clojure.lang.Reflector
when used
together with GraalVM native-image
on java11.
Usage
Include this library in your leiningen profile or deps.edn alias so it's
available on the classpath for GraalVM native-image
.
Important:
- Use the right GraalVM version modifier: e.g.
graalvm-20.3.0
. The modifier must exactly match the version of GraalVMnative-image
you are using. - Do NOT distribute this library as part of libraries or applications that are run with a JVM. Use it for compiling to native binaries only.
Leiningen
Relevant config for project.cljc
:
(defproject foo "0.0.1-SNAPSHOT"
:profiles {:native-image {:dependencies [[borkdude/clj-reflector-graal-java11-fix "0.0.1-graalvm-20.3.0"]]}})
To produce an uberjar that is fed to native-image
you can:
$ lein with-profiles +native-image do clean, uberjar
and then:
$ native-image -jar target/foo-0.0.1-SNAPSHOT-standalone.jar
Tools Deps
Relevant config for deps.edn
:
{:aliases
{:native-image {:extra-deps {borkdude/clj-reflector-graal-java11-fix
{:mvn/version "0.0.1-graalvm-20.3.0"
:exclusions [org.graalvm.nativeimage/svm]}}}}}
Notice the exclusion? This project depends on native dep org.graalvm.nativeimage/svm
.
While technically correct, tools deps does not support native deps and fails for this native dep.
Since the native dep is provided by the Graal installation anyway, it can be safely excluded for tools deps.
Your compile script would get the classspath for native-image
via:
clojure -A:native-image -Spath
The problem
JDK11 is supported since GraalVM 19.3.0. The class clojure.lang.Reflector
uses
a MethodHandle
to maintain compatibility with java8 and java11 at the same
time:
static {
MethodHandle pred = null;
try {
if (! isJava8())
pred = MethodHandles.lookup().findVirtual(Method.class, "canAccess", MethodType.methodType(boolean.class, Object.class));
} catch (Throwable t) {
Util.sneakyThrow(t);
}
CAN_ACCESS_PRED = pred;
}
GraalVM does not support MethodHandle
s that cannot be analyzed as a compile
time constant. It will complain when it analyzes clojure.lang.Reflector
on
JDK11:
Exception in thread "main" com.oracle.svm.core.jdk.UnsupportedFeatureError: Invoke with MethodHandle argument could not be reduced to at most a single call or single field access. The method handle must be a compile time constant, e.g., be loaded from a `static final` field. Method that contains the method handle invocation: java.lang.invoke.Invokers$Holder.invoke_MT(Object, Object, Object, Object)
at com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:101)
at clojure.lang.Reflector.canAccess(Reflector.java:49)
...
See the issue on the GraalVM repo.
The solution
GraalVM supports substitutions. For JDK11 or later the method canAccess
is replaced as follows:
@TargetClass(className = "clojure.lang.Reflector")
final class Target_clojure_lang_Reflector {
@Substitute
@TargetElement(onlyWith = JDK11OrLater.class)
private static boolean canAccess(Method m, Object target) {
// JDK9+ use j.l.r.AccessibleObject::canAccess, which respects module rules
try {
return (boolean) m.canAccess(target);
} catch (Throwable t) {
throw Util.sneakyThrow(t);
}
}
}
License
Copyright © 2020 Michiel Borkent
Distributed under the EPL License. See LICENSE.
This project contains code from:
- Clojure, which is licensed under the same EPL License.