Skip to content
Open
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
Expand Up @@ -30,7 +30,10 @@

package com.google.api.gax.nativeimage;

import static com.google.api.gax.nativeimage.NativeImageUtils.registerClassForJni;
import static com.google.api.gax.nativeimage.NativeImageUtils.registerClassForReflection;
import static com.google.api.gax.nativeimage.NativeImageUtils.registerClassHierarchyForJni;
import static com.google.api.gax.nativeimage.NativeImageUtils.registerClassHierarchyForReflection;

import org.graalvm.nativeimage.hosted.Feature;
import org.jspecify.annotations.NullMarked;
Expand All @@ -51,9 +54,58 @@ final class GoogleJsonClientFeature implements Feature {
public void beforeAnalysis(BeforeAnalysisAccess access) {
loadApiClient(access);
loadHttpClient(access);
loadConscrypt(access);
loadMiscClasses(access);
}

/**
* Registers Conscrypt SSLContext and Security Provider SPI implementation classes (and their
* nested protocol subclasses) for GraalVM reflection, as well as Conscrypt native JNI classes
* and native C shared library resources when Conscrypt is present on the classpath.
*
* <p>When Conscrypt is configured as the security provider for HTTP/JSON transports, Java's JCA
* framework reflectively instantiates provider implementation classes (e.g. {@code
* OpenSSLContextImpl$TLSv13}) via String lookup in {@code SSLContext.getInstance("TLS",
* provider)}. In GraalVM Native Image builds, these reflectively looked-up SPI classes are
* stripped by static analysis unless explicitly registered for reflection, leading to {@code
* ClassNotFoundException} / {@code NoSuchAlgorithmException} at runtime. Additionally, native
* JNI methods and JDK classes accessed by Conscrypt's native C library must be registered for
* GraalVM JNI linkage.
*/
private void loadConscrypt(BeforeAnalysisAccess access) {
Class<?> conscryptClass = access.findClassByName("org.conscrypt.Conscrypt");
if (conscryptClass != null) {
registerClassHierarchyForReflection(access, "org.conscrypt.OpenSSLContextImpl");
registerClassHierarchyForReflection(access, "org.conscrypt.OpenSSLProvider");
registerClassHierarchyForReflection(access, "org.conscrypt.KeyManagerFactoryImpl");
registerClassHierarchyForReflection(access, "org.conscrypt.TrustManagerFactoryImpl");
Comment thread
lqiu96 marked this conversation as resolved.

// Register Conscrypt native JNI bridge classes and hierarchy
registerClassHierarchyForReflection(access, "org.conscrypt.NativeCrypto");
registerClassHierarchyForReflection(access, "org.conscrypt.NativeCryptoJni");
registerClassHierarchyForReflection(access, "org.conscrypt.CryptoUpcalls");
registerClassHierarchyForReflection(access, "org.conscrypt.NativeRef");
registerClassHierarchyForReflection(access, "org.conscrypt.OpenSSLBIOInputStream");

registerClassHierarchyForJni(access, "org.conscrypt.NativeCrypto");
registerClassHierarchyForJni(access, "org.conscrypt.NativeCryptoJni");
registerClassHierarchyForJni(access, "org.conscrypt.CryptoUpcalls");
registerClassHierarchyForJni(access, "org.conscrypt.NativeRef");
registerClassHierarchyForJni(access, "org.conscrypt.OpenSSLBIOInputStream");

// Register standard JDK classes accessed via JNI by Conscrypt native C code
registerClassHierarchyForJni(access, "java.util.Calendar");
registerClassHierarchyForJni(access, "java.io.InputStream");
registerClassHierarchyForJni(access, "java.io.OutputStream");
registerClassHierarchyForJni(access, "java.io.FileDescriptor");
registerClassHierarchyForJni(access, "java.lang.Integer");
registerClassHierarchyForJni(access, "java.lang.String");
registerClassHierarchyForJni(access, "java.lang.Object");
registerClassHierarchyForJni(access, "java.nio.Buffer");
registerClassHierarchyForJni(access, "java.nio.ByteBuffer");
}
}

private void loadApiClient(BeforeAnalysisAccess access) {
// For com.google.api-client:google-api-client
Class<?> googleApiClientClass = access.findClassByName(GOOGLE_API_CLIENT_CLASS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import org.graalvm.nativeimage.hosted.Feature.FeatureAccess;
import org.graalvm.nativeimage.hosted.RuntimeJNIAccess;
import org.graalvm.nativeimage.hosted.RuntimeReflection;
import org.jspecify.annotations.NullMarked;

Expand Down Expand Up @@ -98,6 +99,39 @@ public static void registerClassForReflection(FeatureAccess access, String name)
}
}

/** Registers an entire class for JNI use. */
public static void registerClassForJni(FeatureAccess access, String name) {
Class<?> clazz = access.findClassByName(name);
if (clazz != null) {
RuntimeJNIAccess.register(clazz);
RuntimeJNIAccess.register(clazz.getDeclaredConstructors());
RuntimeJNIAccess.register(clazz.getDeclaredFields());
RuntimeJNIAccess.register(clazz.getDeclaredMethods());
} else {
LOGGER.log(Level.WARNING, CLASS_REFLECTION_ERROR_MESSAGE, name);
}
}

/**
* Registers the transitive class hierarchy of the provided {@code className} for JNI use.
*
* <p>The transitive class hierarchy contains the class itself and its transitive set of
* *non-private* nested subclasses.
*/
public static void registerClassHierarchyForJni(FeatureAccess access, String className) {
Class<?> clazz = access.findClassByName(className);
if (clazz != null) {
registerClassForJni(access, className);
for (Class<?> nestedClass : clazz.getDeclaredClasses()) {
if (!Modifier.isPrivate(nestedClass.getModifiers())) {
registerClassHierarchyForJni(access, nestedClass.getName());
}
}
} else {
LOGGER.log(Level.WARNING, CLASS_REFLECTION_ERROR_MESSAGE, className);
}
}

/**
* Registers the transitive class hierarchy of the provided {@code className} for reflection.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Args = --enable-url-protocols=https,http \
--initialize-at-build-time=org.conscrypt,\
org.junit.platform.engine.TestTag,\
--initialize-at-run-time=org.conscrypt \
--initialize-at-build-time=org.junit.platform.engine.TestTag,\
com.google.api.gax.core.GaxProperties,\
com.google.common.base.Platform,\
com.google.common.base.Platform$JdkPatternCompiler,\
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
{"pattern":"\\Qdependencies.properties\\E"},
{"pattern":"\\Qcom/google/api/client/googleapis/google-api-client.properties\\E"},
{"pattern":"\\Qcom/google/api/client/googleapis/google.p12\\E"},
{"pattern":"\\Qcom/google/api/client/http/google-http-client.properties\\E"}],
{"pattern":"\\Qcom/google/api/client/http/google-http-client.properties\\E"},
{"pattern":".*conscrypt_openjdk_jni.*"}],
"bundles":[]
}
Loading