diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/JniRuntimeAccessBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/JniRuntimeAccessBuildItem.java new file mode 100644 index 0000000000000..fe2efe4714657 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/JniRuntimeAccessBuildItem.java @@ -0,0 +1,58 @@ +package io.quarkus.deployment.builditem.nativeimage; + +import java.util.ArrayList; +import java.util.List; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * Used to register a class for JNI runtime access. + */ +public final class JniRuntimeAccessBuildItem extends MultiBuildItem { + + private final List className; + private final boolean constructors; + private final boolean methods; + private final boolean fields; + private final boolean finalFieldsWriteable; + + public JniRuntimeAccessBuildItem(boolean constructors, boolean methods, boolean fields, Class... classes) { + this(constructors, methods, fields, false, classes); + } + + public JniRuntimeAccessBuildItem(boolean constructors, boolean methods, boolean fields, boolean finalFieldsWriteable, + Class... classes) { + List names = new ArrayList<>(); + for (Class i : classes) { + if (i == null) { + throw new NullPointerException(); + } + names.add(i.getName()); + } + this.className = names; + this.constructors = constructors; + this.methods = methods; + this.fields = fields; + this.finalFieldsWriteable = finalFieldsWriteable; + } + + public List getClassNames() { + return className; + } + + public boolean isConstructors() { + return constructors; + } + + public boolean isMethods() { + return methods; + } + + public boolean isFields() { + return fields; + } + + public boolean isFinalFieldsWriteable() { + return finalFieldsWriteable; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java index 575c7a2ca9aa9..94b772da27598 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java @@ -32,6 +32,7 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.DeploymentClassLoaderBuildItem; import io.quarkus.deployment.builditem.GeneratedNativeImageClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.JniRuntimeAccessBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem; @@ -63,6 +64,7 @@ public class NativeImageAutoFeatureStep { "org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport", "rerunInitialization", void.class, Class.class, String.class); static final String RUNTIME_REFLECTION = RuntimeReflection.class.getName(); + static final String JNI_RUNTIME_ACCESS = "com.oracle.svm.core.jni.JNIRuntimeAccess"; static final String BEFORE_ANALYSIS_ACCESS = Feature.BeforeAnalysisAccess.class.getName(); static final String DYNAMIC_PROXY_REGISTRY = "com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry"; static final String LOCALIZATION_FEATURE = "com.oracle.svm.core.jdk.LocalizationFeature"; @@ -104,7 +106,8 @@ void generateFeature(BuildProducer nativeIma List reflectiveFields, List reflectiveClassBuildItems, List serviceProviderBuildItems, - List unsafeAccessedFields) { + List unsafeAccessedFields, + List jniRuntimeAccessibleClasses) { ClassCreator file = new ClassCreator(new ClassOutput() { @Override public void write(String s, byte[] bytes) { @@ -337,6 +340,62 @@ public void write(String s, byte[] bytes) { //cc.invokeVirtualMethod(ofMethod(Throwable.class, "printStackTrace", void.class), cc.getCaughtException()); mv.returnValue(null); } + + count = 0; + + for (JniRuntimeAccessBuildItem jniAccessible : jniRuntimeAccessibleClasses) { + for (String className : jniAccessible.getClassNames()) { + MethodCreator mv = file.getMethodCreator("registerJniAccessibleClass" + count++, "V"); + mv.setModifiers(Modifier.PRIVATE | Modifier.STATIC); + overallCatch.invokeStaticMethod(mv.getMethodDescriptor()); + + TryBlock tc = mv.tryBlock(); + + ResultHandle currentThread = tc + .invokeStaticMethod(ofMethod(Thread.class, "currentThread", Thread.class)); + ResultHandle tccl = tc.invokeVirtualMethod( + ofMethod(Thread.class, "getContextClassLoader", ClassLoader.class), + currentThread); + ResultHandle clazz = tc.invokeStaticMethod( + ofMethod(Class.class, "forName", Class.class, String.class, boolean.class, ClassLoader.class), + tc.load(className), tc.load(false), tccl); + //we call these methods first, so if they are going to throw an exception it happens before anything has been registered + ResultHandle constructors = tc + .invokeVirtualMethod(ofMethod(Class.class, "getDeclaredConstructors", Constructor[].class), clazz); + ResultHandle methods = tc.invokeVirtualMethod(ofMethod(Class.class, "getDeclaredMethods", Method[].class), + clazz); + ResultHandle fields = tc.invokeVirtualMethod(ofMethod(Class.class, "getDeclaredFields", Field[].class), clazz); + + ResultHandle carray = tc.newArray(Class.class, tc.load(1)); + tc.writeArrayValue(carray, 0, clazz); + tc.invokeStaticMethod(ofMethod(JNI_RUNTIME_ACCESS, "register", void.class, Class[].class), + carray); + + if (jniAccessible.isConstructors()) { + tc.invokeStaticMethod( + ofMethod(JNI_RUNTIME_ACCESS, "register", void.class, Executable[].class), + constructors); + } + + if (jniAccessible.isMethods()) { + tc.invokeStaticMethod( + ofMethod(JNI_RUNTIME_ACCESS, "register", void.class, Executable[].class), + methods); + } + + if (jniAccessible.isFields()) { + tc.invokeStaticMethod( + ofMethod(JNI_RUNTIME_ACCESS, "register", void.class, + boolean.class, Field[].class), + tc.load(jniAccessible.isFinalFieldsWriteable()), fields); + } + + CatchBlockCreator cc = tc.addCatch(Throwable.class); + //cc.invokeVirtualMethod(ofMethod(Throwable.class, "printStackTrace", void.class), cc.getCaughtException()); + mv.returnValue(null); + } + } + CatchBlockCreator print = overallCatch.addCatch(Throwable.class); print.invokeVirtualMethod(ofMethod(Throwable.class, "printStackTrace", void.class), print.getCaughtException()); diff --git a/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsProcessor.java b/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsProcessor.java index 07dbd0389aaf9..38ee19609cc32 100644 --- a/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsProcessor.java +++ b/extensions/kafka-streams/deployment/src/main/java/io/quarkus/kafka/streams/deployment/KafkaStreamsProcessor.java @@ -13,6 +13,8 @@ import org.apache.kafka.streams.processor.internals.StreamsPartitionAssignor; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; +import org.rocksdb.RocksDBException; +import org.rocksdb.Status; import org.rocksdb.util.Environment; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; @@ -23,6 +25,7 @@ import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.builditem.nativeimage.JniRuntimeAccessBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeReinitializedClassBuildItem; @@ -41,6 +44,7 @@ class KafkaStreamsProcessor { @BuildStep void build(BuildProducer feature, BuildProducer reflectiveClasses, + BuildProducer jniRuntimeAccessibleClasses, BuildProducer reinitialized, BuildProducer nativeLibs, LaunchModeBuildItem launchMode, @@ -49,6 +53,7 @@ void build(BuildProducer feature, feature.produce(new FeatureBuildItem(FeatureBuildItem.KAFKA_STREAMS)); registerClassesThatAreLoadedThroughReflection(reflectiveClasses, launchMode); + registerClassesThatAreAccessedViaJni(jniRuntimeAccessibleClasses); addSupportForRocksDbLib(nativeLibs, config); enableLoadOfNativeLibs(reinitialized); } @@ -105,6 +110,11 @@ private void registerDefaultSerdes(BuildProducer refle } } + private void registerClassesThatAreAccessedViaJni(BuildProducer jniRuntimeAccessibleClasses) { + jniRuntimeAccessibleClasses + .produce(new JniRuntimeAccessBuildItem(true, false, false, RocksDBException.class, Status.class)); + } + private void addSupportForRocksDbLib(BuildProducer nativeLibs, NativeConfig nativeConfig) { // for RocksDB, either add linux64 native lib when targeting containers if (nativeConfig.containerRuntime.isPresent() || nativeConfig.containerBuild) {