Skip to content

Commit

Permalink
Merge pull request #7329 from gunnarmorling/7066
Browse files Browse the repository at this point in the history
Add support for registering classes for JNI runtime access
  • Loading branch information
gsmet authored Feb 22, 2020
2 parents 905595f + bd6ab90 commit 8aaae58
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -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<String> 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<String> 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<String> getClassNames() {
return className;
}

public boolean isConstructors() {
return constructors;
}

public boolean isMethods() {
return methods;
}

public boolean isFields() {
return fields;
}

public boolean isFinalFieldsWriteable() {
return finalFieldsWriteable;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -104,7 +106,8 @@ void generateFeature(BuildProducer<GeneratedNativeImageClassBuildItem> nativeIma
List<ReflectiveFieldBuildItem> reflectiveFields,
List<ReflectiveClassBuildItem> reflectiveClassBuildItems,
List<ServiceProviderBuildItem> serviceProviderBuildItems,
List<UnsafeAccessedFieldBuildItem> unsafeAccessedFields) {
List<UnsafeAccessedFieldBuildItem> unsafeAccessedFields,
List<JniRuntimeAccessBuildItem> jniRuntimeAccessibleClasses) {
ClassCreator file = new ClassCreator(new ClassOutput() {
@Override
public void write(String s, byte[] bytes) {
Expand Down Expand Up @@ -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());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -41,6 +44,7 @@ class KafkaStreamsProcessor {
@BuildStep
void build(BuildProducer<FeatureBuildItem> feature,
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses,
BuildProducer<JniRuntimeAccessBuildItem> jniRuntimeAccessibleClasses,
BuildProducer<RuntimeReinitializedClassBuildItem> reinitialized,
BuildProducer<NativeImageResourceBuildItem> nativeLibs,
LaunchModeBuildItem launchMode,
Expand All @@ -49,6 +53,7 @@ void build(BuildProducer<FeatureBuildItem> feature,
feature.produce(new FeatureBuildItem(FeatureBuildItem.KAFKA_STREAMS));

registerClassesThatAreLoadedThroughReflection(reflectiveClasses, launchMode);
registerClassesThatAreAccessedViaJni(jniRuntimeAccessibleClasses);
addSupportForRocksDbLib(nativeLibs, config);
enableLoadOfNativeLibs(reinitialized);
}
Expand Down Expand Up @@ -105,6 +110,11 @@ private void registerDefaultSerdes(BuildProducer<ReflectiveClassBuildItem> refle
}
}

private void registerClassesThatAreAccessedViaJni(BuildProducer<JniRuntimeAccessBuildItem> jniRuntimeAccessibleClasses) {
jniRuntimeAccessibleClasses
.produce(new JniRuntimeAccessBuildItem(true, false, false, RocksDBException.class, Status.class));
}

private void addSupportForRocksDbLib(BuildProducer<NativeImageResourceBuildItem> nativeLibs, NativeConfig nativeConfig) {
// for RocksDB, either add linux64 native lib when targeting containers
if (nativeConfig.containerRuntime.isPresent() || nativeConfig.containerBuild) {
Expand Down

0 comments on commit 8aaae58

Please sign in to comment.