Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.quarkus.deployment.builditem.nativeimage;

import io.quarkus.builder.item.MultiBuildItem;

/**
* Used to register a lambda capturing type in native mode
*/
public final class LambdaCapturingTypeBuildItem extends MultiBuildItem {

private final String className;

public LambdaCapturingTypeBuildItem(Class<?> lambdaCapturingType) {
this.className = lambdaCapturingType.getName();
}

public LambdaCapturingTypeBuildItem(String className) {
this.className = className;
}

public String getClassName() {
return className;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import io.quarkus.deployment.builditem.GeneratedResourceBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ForceNonWeakReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.JniRuntimeAccessBuildItem;
import io.quarkus.deployment.builditem.nativeimage.LambdaCapturingTypeBuildItem;
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 @@ -72,6 +73,17 @@ public class NativeImageAutoFeatureStep {
private static final MethodDescriptor RERUN_INITIALIZATION = ofMethod(
"org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport",
"rerunInitialization", void.class, Class.class, String.class);

private static final MethodDescriptor CONFIGURATION_ALWAYS_TRUE = ofMethod(
"org.graalvm.nativeimage.impl.ConfigurationCondition",
"alwaysTrue", "org.graalvm.nativeimage.impl.ConfigurationCondition");

private static final MethodDescriptor REGISTER_LAMBDA_CAPTURING_CLASS = ofMethod(
"org.graalvm.nativeimage.impl.RuntimeSerializationSupport",
"registerLambdaCapturingClass", void.class,
"org.graalvm.nativeimage.impl.ConfigurationCondition",
String.class);

private static final MethodDescriptor LOOKUP_METHOD = ofMethod(
"com.oracle.svm.util.ReflectionUtil",
"lookupMethod", Method.class, Class.class, String.class, Class[].class);
Expand All @@ -82,6 +94,7 @@ public class NativeImageAutoFeatureStep {
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 DURING_SETUP_ACCESS = Feature.DuringSetupAccess.class.getName();
static final String DYNAMIC_PROXY_REGISTRY = "com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry";
static final String LEGACY_LOCALIZATION_FEATURE = "com.oracle.svm.core.jdk.LocalizationFeature";
static final String LOCALIZATION_FEATURE = "com.oracle.svm.core.jdk.localization.LocalizationFeature";
Expand Down Expand Up @@ -120,7 +133,8 @@ void generateFeature(BuildProducer<GeneratedNativeImageClassBuildItem> nativeIma
List<ForceNonWeakReflectiveClassBuildItem> nonWeakReflectiveClassBuildItems,
List<ServiceProviderBuildItem> serviceProviderBuildItems,
List<UnsafeAccessedFieldBuildItem> unsafeAccessedFields,
List<JniRuntimeAccessBuildItem> jniRuntimeAccessibleClasses) {
List<JniRuntimeAccessBuildItem> jniRuntimeAccessibleClasses,
List<LambdaCapturingTypeBuildItem> lambdaCapturingTypeBuildItems) {
ClassCreator file = new ClassCreator(new ClassOutput() {
@Override
public void write(String s, byte[] bytes) {
Expand All @@ -130,7 +144,27 @@ public void write(String s, byte[] bytes) {
Object.class.getName(), Feature.class.getName());
file.addAnnotation("com.oracle.svm.core.annotate.AutomaticFeature");

//MethodCreator afterReg = file.getMethodCreator("afterRegistration", void.class, "org.graalvm.nativeimage.Feature$AfterRegistrationAccess");
MethodCreator duringSetup = file.getMethodCreator("duringSetup", "V", DURING_SETUP_ACCESS);
// Register Lambda Capturing Types
if (!lambdaCapturingTypeBuildItems.isEmpty()) {
ResultHandle runtimeSerializationSupportSingleton = duringSetup.invokeStaticMethod(IMAGE_SINGLETONS_LOOKUP,
duringSetup.loadClassFromTCCL("org.graalvm.nativeimage.impl.RuntimeSerializationSupport"));
ResultHandle configAlwaysTrue = duringSetup.invokeStaticMethod(CONFIGURATION_ALWAYS_TRUE);

for (LambdaCapturingTypeBuildItem i : lambdaCapturingTypeBuildItems) {
TryBlock tryBlock = duringSetup.tryBlock();

tryBlock.invokeInterfaceMethod(REGISTER_LAMBDA_CAPTURING_CLASS, runtimeSerializationSupportSingleton,
configAlwaysTrue,
tryBlock.load(i.getClassName()));

CatchBlockCreator catchBlock = tryBlock.addCatch(Throwable.class);
catchBlock.invokeVirtualMethod(ofMethod(Throwable.class, "printStackTrace", void.class),
catchBlock.getCaughtException());
}
}
duringSetup.returnValue(null);

MethodCreator beforeAn = file.getMethodCreator("beforeAnalysis", "V", BEFORE_ANALYSIS_ACCESS);
TryBlock overallCatch = beforeAn.tryBlock();
//TODO: at some point we are going to need to break this up, as if it get too big it will hit the method size limit
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.quarkus.deployment.steps;

import javax.inject.Inject;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
Expand All @@ -12,18 +10,19 @@
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.nativeimage.LambdaCapturingTypeBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.runtime.annotations.RegisterForReflection;

public class RegisterForReflectionBuildStep {

private static final Logger log = Logger.getLogger(RegisterForReflectionBuildStep.class);

@Inject
CombinedIndexBuildItem combinedIndexBuildItem;

@BuildStep
public void build(BuildProducer<ReflectiveClassBuildItem> reflectiveClass) {
public void build(CombinedIndexBuildItem combinedIndexBuildItem,
BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
BuildProducer<LambdaCapturingTypeBuildItem> lambdaCapturingTypeProducer) {

for (AnnotationInstance i : combinedIndexBuildItem.getIndex()
.getAnnotations(DotName.createSimple(RegisterForReflection.class.getName()))) {

Expand All @@ -34,6 +33,13 @@ public void build(BuildProducer<ReflectiveClassBuildItem> reflectiveClass) {

AnnotationValue targetsValue = i.value("targets");
AnnotationValue classNamesValue = i.value("classNames");
AnnotationValue lambdaCapturingTypesValue = i.value("lambdaCapturingTypes");

if (lambdaCapturingTypesValue != null) {
for (String lambdaCapturingType : lambdaCapturingTypesValue.asStringArray()) {
lambdaCapturingTypeProducer.produce(new LambdaCapturingTypeBuildItem(lambdaCapturingType));
}
}

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (targetsValue == null && classNamesValue == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,9 @@
String[] classNames() default {};

boolean serialization() default false;

/**
* The lambda capturing types performing serialization in the native image
*/
String[] lambdaCapturingTypes() default {};
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,17 @@ public String getSimpleClassName(@QueryParam("className") String className) {
return "FAILED";
}
}

@GET
@Path("/lambda")
public String getLambdaClassName() {
try {
ResourceLambda lambda = new ResourceLambda();
Class<?> clazz = lambda.getLambdaFuncClass(5);
return clazz.getSimpleName();
} catch (Exception e) {
return e.toString();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.quarkus.it.rest;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.util.Comparator;

import io.quarkus.runtime.annotations.RegisterForReflection;

/**
* This class is registering for lambda capturing
*/
@RegisterForReflection(lambdaCapturingTypes = "java.util.Comparator", targets = {
SerializedLambda.class, SerializableDoubleFunction.class }, serialization = true)
public class ResourceLambda {

public Class<?> getLambdaFuncClass(Integer n) throws IOException, ClassNotFoundException {
SerializableDoubleFunction func = new SerializableDoubleFunction(n);
Comparator<Integer> comp = Comparator.comparingDouble(func);

ByteArrayOutputStream out = new ByteArrayOutputStream();
serializeObject(out, (Serializable) comp);
return deserializeObject(out).getClass();
}

private void serializeObject(ByteArrayOutputStream byteArrayOutputStream, Serializable o) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(o);
objectOutputStream.close();
}

private Object deserializeObject(ByteArrayOutputStream byteArrayOutputStream) throws IOException, ClassNotFoundException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkus.it.rest;

import java.io.Serializable;
import java.util.function.ToDoubleFunction;

class SerializableDoubleFunction implements Serializable, ToDoubleFunction<Integer> {
private final double value;

public SerializableDoubleFunction(double inputValue) {
value = inputValue;
}

@Override
public double applyAsDouble(Integer o) {
return value;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.it.main;

import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;

import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -68,6 +69,16 @@ public void testTargetWithoutNested() {
assertRegistration("FAILED", resourceD + "$StaticClassOfD$OtherAccessibleClassOfD");
}

@Test
@DisableIfBuiltWithGraalVMOlderThan(GraalVMVersion.GRAALVM_22_1)
public void testLambdaCapturing() {
final String resourceLambda = BASE_PKG + ".ResourceLambda";

// Starting with GraalVM 22.1 support Lambda functions serialization
// (see https://github.com/oracle/graal/issues/3756)
RestAssured.given().when().get("/reflection/lambda").then().body(startsWith("Comparator$$Lambda$"));
}

private void assertRegistration(String expected, String queryParam) {
RestAssured.given().queryParam("className", queryParam).when().get(ENDPOINT).then().body(is(expected));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.it.main;

import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;

import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -51,6 +52,14 @@ public void testTargetWithoutNested() {
assertRegistration("OtherAccessibleClassOfD", resourceD + "$StaticClassOfD$OtherAccessibleClassOfD");
}

@Test
public void testLambdaCapturing() {
final String resourceLambda = BASE_PKG + ".ResourceLambda";

assertRegistration("ResourceLambda", resourceLambda);
RestAssured.given().when().get("/reflection/lambda").then().body(startsWith("Comparator$$Lambda$"));
}

private void assertRegistration(String expected, String queryParam) {
RestAssured.given().queryParam("className", queryParam).when().get(ENDPOINT).then().body(is(expected));
}
Expand Down