From f14432b9d8be8e2d8c3aa19ab27fe014b2a7f2f0 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Tue, 15 Mar 2022 12:41:20 +0100 Subject: [PATCH] Support for Unsafe.allocateInstance in NI --- .../nativeimage/impl/ReflectionRegistry.java | 9 +- .../svm/agent/BreakpointInterceptor.java | 192 +++++++++++++++++- .../oracle/svm/agent/NativeImageAgent.java | 7 +- .../configure/config/ConfigurationType.java | 8 + .../config/ParserConfigurationAdapter.java | 5 + .../configure/trace/ReflectionProcessor.java | 4 + .../ReflectionConfigurationParser.java | 7 +- ...ReflectionConfigurationParserDelegate.java | 2 + .../snippets/SubstrateAllocationSnippets.java | 12 +- .../config/ReflectionRegistryAdapter.java | 5 + .../svm/jni/access/JNIAccessFeature.java | 6 +- .../reflect/hosted/ReflectionDataBuilder.java | 17 +- vm/mx.vm/ni-ce | 2 +- 13 files changed, 250 insertions(+), 26 deletions(-) diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java index 8fa0be7b8b2e..72ef3450ee69 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -42,9 +42,14 @@ import java.lang.reflect.Executable; import java.lang.reflect.Field; +import java.util.Arrays; public interface ReflectionRegistry { - void register(ConfigurationCondition condition, Class... classes); + default void register(ConfigurationCondition condition, Class... classes) { + Arrays.stream(classes).forEach(clazz -> register(condition, false, clazz)); + } + + void register(ConfigurationCondition condition, boolean unsafeAllocated, Class clazz); void register(ConfigurationCondition condition, boolean queriedOnly, Executable... methods); diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index 841214b126e5..87a18ba61809 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -27,6 +27,7 @@ import static com.oracle.svm.core.util.VMError.guarantee; import static com.oracle.svm.jni.JNIObjectHandles.nullHandle; import static com.oracle.svm.jvmtiagentbase.Support.check; +import static com.oracle.svm.jvmtiagentbase.Support.checkJni; import static com.oracle.svm.jvmtiagentbase.Support.checkNoException; import static com.oracle.svm.jvmtiagentbase.Support.clearException; import static com.oracle.svm.jvmtiagentbase.Support.fromCString; @@ -44,6 +45,7 @@ import static com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEvent.JVMTI_EVENT_BREAKPOINT; import static com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEvent.JVMTI_EVENT_CLASS_FILE_LOAD_HOOK; import static com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEvent.JVMTI_EVENT_CLASS_PREPARE; +import static com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEvent.JVMTI_EVENT_NATIVE_METHOD_BIND; import static org.graalvm.word.WordFactory.nullPointer; import java.nio.ByteBuffer; @@ -55,14 +57,18 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; +import com.oracle.svm.jni.nativeapi.JNINativeMethod; import org.graalvm.compiler.core.common.NumUtil; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.UnmanagedMemory; import org.graalvm.nativeimage.c.function.CEntryPoint; import org.graalvm.nativeimage.c.function.CEntryPointLiteral; import org.graalvm.nativeimage.c.function.CFunctionPointer; +import org.graalvm.nativeimage.c.function.CodePointer; +import org.graalvm.nativeimage.c.function.InvokeCFunctionPointer; import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CCharPointerPointer; @@ -93,6 +99,7 @@ import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEventMode; import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiFrameInfo; import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiLocationFormat; +import org.graalvm.word.WordFactory; /** * Intercepts events of interest via breakpoints in Java code. @@ -121,11 +128,18 @@ final class BreakpointInterceptor { private static Map installedBreakpoints; - /* - * NOTE: support for breakpoints in native methods has been removed. - * - * Restore from version control if needed. + /** + * A map from {@link JNIMethodId} to entry point addresses for bound Java {@code native} + * methods, NOT considering our intercepting functions, i.e., these are the original entry + * points for a native method from symbol resolution or {@code registerNatives}. + */ + private static Map boundNativeMethods; + + /** + * Map from {@link JNIMethodId} to breakpoints in {@code native} methods. Not all of them may be + * installed if the native methods haven't been {@linkplain #bindNativeBreakpoint bound}. */ + private static Map nativeBreakpoints; /** Enables experimental support for instrumenting class lookups via {@code ClassLoader}. */ private static boolean experimentalClassLoaderSupport = false; @@ -133,6 +147,9 @@ final class BreakpointInterceptor { /** Enables experimental support for class definitions via {@code ClassLoader.defineClass}. */ private static boolean experimentalClassDefineSupport = false; + /** Enables experimental support for tracking {@code Unsafe.allocateInstance}. */ + private static boolean experimentalUnsafeAllocationSupport = false; + /** Enables tracking of reflection queries for fine-tuned configuration. */ private static boolean trackReflectionMetadata = false; @@ -141,6 +158,12 @@ final class BreakpointInterceptor { */ private static ConcurrentMap observedExplicitLoadClassCallSites; + /** + * Guards access to {@link #boundNativeMethods} and {@link #nativeBreakpoints} to avoid races + * that cause breakpoints to not be installed. + */ + private static final ReentrantLock nativeBreakpointsInitLock = new ReentrantLock(); + private static final ThreadLocal recursive = ThreadLocal.withInitial(() -> Boolean.FALSE); /* Classes from these class loaders are assumed to not be dynamically loaded. */ @@ -303,6 +326,43 @@ private static boolean handleGetField(JNIEnvironment jni, Breakpoint bp, boolean return true; } + private static final CEntryPointLiteral nativeAllocateInstance = CEntryPointLiteral.create( + BreakpointInterceptor.class, "nativeAllocateInstance", JNIEnvironment.class, JNIObjectHandle.class, JNIObjectHandle.class); + + private static final NativeBreakpointSpecification NATIVE_ALLOCATE_INSTANCE_BREAKPOINT_SPEC = new NativeBreakpointSpecification( + "jdk/internal/misc/Unsafe", "allocateInstance", "(Ljava/lang/Class;)Ljava/lang/Object;", nativeAllocateInstance); + + private interface AllocateInstanceFunctionPointer extends CFunctionPointer { + @InvokeCFunctionPointer + long invoke(JNIEnvironment jni, JNIObjectHandle self, JNIObjectHandle field); + } + + /** Native breakpoint for the {@code jdk/internal/misc/Unsafe#allocateInstance} method. */ + @CEntryPoint + @CEntryPointOptions(prologue = AgentIsolate.Prologue.class) + static long nativeAllocateInstance(JNIEnvironment jni, JNIObjectHandle self, JNIObjectHandle clazz) { + VMError.guarantee(NATIVE_ALLOCATE_INSTANCE_BREAKPOINT_SPEC.installed != null && + NATIVE_ALLOCATE_INSTANCE_BREAKPOINT_SPEC.installed.replacedFunction.isNonNull(), "incompletely installed"); + + AllocateInstanceFunctionPointer original = (AllocateInstanceFunctionPointer) NATIVE_ALLOCATE_INSTANCE_BREAKPOINT_SPEC.installed.replacedFunction; + long result = original.invoke(jni, self, clazz); + if (!Support.isInitialized()) { // in case of a (very) late call + return result; + } + boolean validResult = !clearException(jni); + InterceptedState state = interceptedStateSupplier.get(); + JNIObjectHandle callerClass = state.getDirectCallerClass(); + if (clazz.notEqual(nullHandle())) { + if (!clearException(jni)) { + traceReflectBreakpoint(jni, clazz, nullHandle(), callerClass, "allocateInstance", validResult, state.getFullStackTraceOrNull()); + } + } + if (!validResult) { // invoke again for exception--pure function. + return original.invoke(jni, self, clazz); + } + return result; + } + private static boolean objectFieldOffsetByName(JNIEnvironment jni, Breakpoint bp, InterceptedState state) { JNIObjectHandle callerClass = state.getDirectCallerClass(); JNIObjectHandle self = getObjectArgument(0); @@ -1183,6 +1243,28 @@ private static void onBreakpoint(@SuppressWarnings("unused") JvmtiEnv jvmti, JNI } } + @CEntryPoint + @CEntryPointOptions(prologue = AgentIsolate.Prologue.class) + private static void onNativeMethodBind(@SuppressWarnings("unused") JvmtiEnv jvmti, JNIEnvironment jni, + @SuppressWarnings("unused") JNIObjectHandle thread, JNIMethodId method, CodePointer address, WordPointer newAddressPtr) { + if (recursive.get()) { + return; + } + nativeBreakpointsInitLock.lock(); + try { + if (nativeBreakpoints != null) { + NativeBreakpoint bp = nativeBreakpoints.get(method.rawValue()); + if (bp != null) { + bindNativeBreakpoint(jni, bp, address, newAddressPtr); + } + } else { // breakpoints are not yet initialized, remember and install breakpoint later + boundNativeMethods.put(method.rawValue(), address.rawValue()); + } + } finally { + nativeBreakpointsInitLock.unlock(); + } + } + @CEntryPoint @CEntryPointOptions(prologue = AgentIsolate.Prologue.class) private static void onClassPrepare(@SuppressWarnings("unused") JvmtiEnv jvmti, JNIEnvironment jni, @@ -1237,6 +1319,9 @@ private static void onClassFileLoadHook(@SuppressWarnings("unused") JvmtiEnv jvm private static final CEntryPointLiteral onBreakpointLiteral = CEntryPointLiteral.create(BreakpointInterceptor.class, "onBreakpoint", JvmtiEnv.class, JNIEnvironment.class, JNIObjectHandle.class, JNIMethodId.class, long.class); + private static final CEntryPointLiteral onNativeMethodBindLiteral = CEntryPointLiteral.create(BreakpointInterceptor.class, "onNativeMethodBind", + JvmtiEnv.class, JNIEnvironment.class, JNIObjectHandle.class, JNIMethodId.class, CodePointer.class, WordPointer.class); + private static final CEntryPointLiteral onClassPrepareLiteral = CEntryPointLiteral.create(BreakpointInterceptor.class, "onClassPrepare", JvmtiEnv.class, JNIEnvironment.class, JNIObjectHandle.class, JNIObjectHandle.class); @@ -1246,18 +1331,26 @@ private static void onClassFileLoadHook(@SuppressWarnings("unused") JvmtiEnv jvm public static void onLoad(JvmtiEnv jvmti, JvmtiEventCallbacks callbacks, Tracer writer, NativeImageAgent nativeImageTracingAgent, Supplier currentThreadJavaStackAccessSupplier, - boolean exptlClassLoaderSupport, boolean exptlClassDefineSupport, boolean trackReflectionData) { + boolean exptlClassLoaderSupport, boolean exptlClassDefineSupport, boolean exptlUnsafeAllocationSupport, boolean trackReflectionData) { BreakpointInterceptor.tracer = writer; BreakpointInterceptor.agent = nativeImageTracingAgent; BreakpointInterceptor.interceptedStateSupplier = currentThreadJavaStackAccessSupplier; BreakpointInterceptor.experimentalClassLoaderSupport = exptlClassLoaderSupport; BreakpointInterceptor.experimentalClassDefineSupport = exptlClassDefineSupport; + BreakpointInterceptor.experimentalUnsafeAllocationSupport = exptlUnsafeAllocationSupport; BreakpointInterceptor.trackReflectionMetadata = trackReflectionData; JvmtiCapabilities capabilities = UnmanagedMemory.calloc(SizeOf.get(JvmtiCapabilities.class)); check(jvmti.getFunctions().GetCapabilities().invoke(jvmti, capabilities)); capabilities.setCanGenerateBreakpointEvents(1); capabilities.setCanAccessLocalVariables(1); + + if (exptlUnsafeAllocationSupport) { + capabilities.setCanGenerateNativeMethodBindEvents(1); + callbacks.setNativeMethodBind(onNativeMethodBindLiteral.getFunctionPointer()); + BreakpointInterceptor.boundNativeMethods = new HashMap<>(); + } + if (exptlClassLoaderSupport) { capabilities.setCanGetBytecodes(1); capabilities.setCanGetConstantPool(1); @@ -1270,12 +1363,18 @@ public static void onLoad(JvmtiEnv jvmti, JvmtiEventCallbacks callbacks, Tracer UnmanagedMemory.free(capabilities); callbacks.setBreakpoint(onBreakpointLiteral.getFunctionPointer()); + if (exptlClassDefineSupport) { callbacks.setClassFileLoadHook(onClassFileLoadHookLiteral.getFunctionPointer()); } + if (exptlClassLoaderSupport) { callbacks.setClassPrepare(onClassPrepareLiteral.getFunctionPointer()); } + + if (exptlUnsafeAllocationSupport) { + Support.check(jvmti.getFunctions().SetEventNotificationMode().invoke(jvmti, JvmtiEventMode.JVMTI_ENABLE, JVMTI_EVENT_NATIVE_METHOD_BIND, nullHandle())); + } } public static void onVMInit(JvmtiEnv jvmti, JNIEnvironment jni) { @@ -1320,6 +1419,10 @@ public static void onVMInit(JvmtiEnv jvmti, JNIEnvironment jni) { } installedBreakpoints = breakpoints; + if (experimentalUnsafeAllocationSupport) { + setupNativeBreakpoints(jni, lastClass, lastClassName); + } + if (experimentalClassDefineSupport) { setupClassLoadEvent(jvmti, jni); } @@ -1330,6 +1433,37 @@ public static void onVMInit(JvmtiEnv jvmti, JNIEnvironment jni) { } } + private static void setupNativeBreakpoints(JNIEnvironment jni, JNIObjectHandle previousClass, String previousClassName) { + JNIObjectHandle lastClass = previousClass; + String lastClassName = previousClassName; + nativeBreakpointsInitLock.lock(); + try { + nativeBreakpoints = new HashMap<>(NATIVE_BREAKPOINT_SPECIFICATIONS.length); + for (NativeBreakpointSpecification br : NATIVE_BREAKPOINT_SPECIFICATIONS) { + JNIObjectHandle clazz; + if (lastClassName != null && lastClassName.equals(br.className)) { + clazz = lastClass; + } else { + clazz = resolveBreakpointClass(jni, br.className, br.optional); + lastClass = clazz; + lastClassName = br.className; + } + JNIMethodId method = resolveBreakpointMethod(jni, clazz, br.methodName, br.signature, br.optional); + if (method.isNonNull()) { + NativeBreakpoint bp = new NativeBreakpoint(br, clazz, method); + nativeBreakpoints.put(method.rawValue(), bp); + Long original = boundNativeMethods.get(method.rawValue()); + if (original != null) { // already bound, replace + bindNativeBreakpoint(jni, bp, WordFactory.pointer(original), nullPointer()); + } + } + } + boundNativeMethods = null; + } finally { + nativeBreakpointsInitLock.unlock(); + } + } + private static void setupClassLoadEvent(JvmtiEnv jvmti, JNIEnvironment jni) { JNIObjectHandle classLoader = agent.handles().javaLangClassLoader; @@ -1447,9 +1581,32 @@ public static JNIObjectHandle getClassOrSingleProxyInterface(JNIEnvironment env, return iface; } + private static void bindNativeBreakpoint(JNIEnvironment jni, NativeBreakpoint bp, CodePointer originalAddress, WordPointer newAddressPtr) { + assert !recursive.get(); + bp.replacedFunction = originalAddress; + CFunctionPointer breakpointMethod = bp.specification.handlerLiteral.getFunctionPointer(); + if (newAddressPtr.isNonNull()) { + newAddressPtr.write(breakpointMethod); + } else { + recursive.set(true); + try (CCharPointerHolder cname = toCString(bp.specification.methodName); + CCharPointerHolder csignature = toCString(bp.specification.signature)) { + + JNINativeMethod nativeMethod = StackValue.get(JNINativeMethod.class); + nativeMethod.setName(cname.get()); + nativeMethod.setSignature(csignature.get()); + nativeMethod.setFnPtr(breakpointMethod); + checkJni(jni.getFunctions().getRegisterNatives().invoke(jni, bp.clazz, nativeMethod, 1)); + } finally { + recursive.set(false); + } + } + } + public static void onUnload() { builtinClassLoaders = null; installedBreakpoints = null; + nativeBreakpoints = null; observedExplicitLoadClassCallSites = null; tracer = null; } @@ -1578,6 +1735,10 @@ private interface BreakpointHandler { private static final BreakpointSpecification CLASSLOADER_LOAD_CLASS_BREAKPOINT_SPECIFICATION = optionalBrk("java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", BreakpointInterceptor::loadClass); + private static final NativeBreakpointSpecification[] NATIVE_BREAKPOINT_SPECIFICATIONS = { + NATIVE_ALLOCATE_INSTANCE_BREAKPOINT_SPEC + }; + private static final BreakpointSpecification[] REFLECTION_QUERIES_BREAKPOINT_SPECIFICATIONS = { brk("java/lang/Class", "getMethods", "()[Ljava/lang/reflect/Method;", BreakpointInterceptor::getMethods), brk("java/lang/Class", "getConstructors", "()[Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getConstructors), @@ -1598,6 +1759,16 @@ private static BreakpointSpecification optionalBrk(String className, String meth return new BreakpointSpecification(className, methodName, signature, handler, true); } + private static class NativeBreakpointSpecification extends AbstractBreakpointSpecification { + final CEntryPointLiteral handlerLiteral; + NativeBreakpoint installed; + + NativeBreakpointSpecification(String className, String methodName, String signature, CEntryPointLiteral handlerLiteral) { + super(className, methodName, signature, true); + this.handlerLiteral = handlerLiteral; + } + } + private abstract static class AbstractBreakpointSpecification { final String className; final String methodName; @@ -1649,6 +1820,17 @@ private static class Breakpoint extends AbstractBreakpoint { + CodePointer replacedFunction; + + NativeBreakpoint(NativeBreakpointSpecification specification, JNIObjectHandle clazz, JNIMethodId method) { + super(specification, clazz, method); + + assert specification.installed == null : "must be installed exactly once"; + specification.installed = this; + } + } + private static final class MethodLocation { final JNIMethodId method; final int bci; diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java index 2141bc9df29a..05890dffe578 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java @@ -127,6 +127,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c List accessFilterFiles = new ArrayList<>(); boolean experimentalClassLoaderSupport = true; boolean experimentalClassDefineSupport = false; + boolean experimentalUnsafeAllocationSupport = false; boolean experimentalOmitClasspathConfig = false; boolean build = false; boolean configurationWithOrigins = false; @@ -187,6 +188,10 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c experimentalClassDefineSupport = true; } else if (token.startsWith("experimental-class-define-support=")) { experimentalClassDefineSupport = Boolean.parseBoolean(getTokenValue(token)); + } else if (token.equals("experimental-unsafe-allocation-support")) { + experimentalUnsafeAllocationSupport = Boolean.parseBoolean(getTokenValue(token)); + } else if (token.startsWith("experimental-unsafe-allocation-support=")) { + experimentalUnsafeAllocationSupport = Boolean.parseBoolean(getTokenValue(token)); } else if (token.startsWith("config-write-period-secs=")) { configWritePeriod = parseIntegerOrNegative(getTokenValue(token)); if (configWritePeriod <= 0) { @@ -370,7 +375,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c try { BreakpointInterceptor.onLoad(jvmti, callbacks, tracer, this, interceptedStateSupplier, - experimentalClassLoaderSupport, experimentalClassDefineSupport, trackReflectionMetadata); + experimentalClassLoaderSupport, experimentalClassDefineSupport, experimentalUnsafeAllocationSupport, trackReflectionMetadata); } catch (Throwable t) { return error(3, t.toString()); } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java index 03f1f78b1b8f..769eb7347ef2 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java @@ -96,6 +96,7 @@ static ConfigurationType copyAndMerge(ConfigurationType type, ConfigurationType private boolean allPublicClasses; private boolean allDeclaredFields; private boolean allPublicFields; + private boolean unsafeAllocated; private ConfigurationMemberAccessibility allDeclaredMethodsAccess = ConfigurationMemberAccessibility.NONE; private ConfigurationMemberAccessibility allPublicMethodsAccess = ConfigurationMemberAccessibility.NONE; private ConfigurationMemberAccessibility allDeclaredConstructorsAccess = ConfigurationMemberAccessibility.NONE; @@ -269,6 +270,7 @@ private void setFlagsFromOther(ConfigurationType other, BiPredicate methodParameterTypes) { type.addMethod(methodName, ConfigurationMethod.toInternalParamsSignature(methodParameterTypes), ConfigurationMemberDeclaration.PRESENT, diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java index 6a50c0af5b03..149832e9a5c1 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java @@ -256,6 +256,10 @@ public void processEntry(Map entry, ConfigurationSet configurationSet resourceConfiguration.addBundle(condition, classNames, locales, baseName); break; } + case "allocateInstance": { + configuration.getOrCreateType(condition, clazz).setUnsafeAllocated(); + break; + } default: System.err.println("Unsupported reflection method: " + function); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java index f7aefb9edc2b..a7f1f9d09075 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java @@ -50,7 +50,7 @@ public final class ReflectionConfigurationParser extends ConfigurationParser private static final List OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS = Arrays.asList("allDeclaredConstructors", "allPublicConstructors", "allDeclaredMethods", "allPublicMethods", "allDeclaredFields", "allPublicFields", "allDeclaredClasses", "allPermittedSubclasses", "allPublicClasses", "methods", "queriedMethods", "fields", CONDITIONAL_KEY, - "queryAllDeclaredConstructors", "queryAllPublicConstructors", "queryAllDeclaredMethods", "queryAllPublicMethods"); + "queryAllDeclaredConstructors", "queryAllPublicConstructors", "queryAllDeclaredMethods", "queryAllPublicMethods", "unsafeAllocated"); public ReflectionConfigurationParser(ReflectionConfigurationParserDelegate delegate) { this(delegate, true); @@ -165,6 +165,11 @@ private void parseClass(Map data) { delegate.registerPublicMethods(true, clazz); } break; + case "unsafeAllocated": + if (asBoolean(value, "unsafeAllocated")) { + delegate.registerUnsafeAllocated(clazz); + } + break; case "methods": parseMethods(false, asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz); break; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java index f21bda93a86c..20c4a645a1c0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java @@ -66,6 +66,8 @@ public interface ReflectionConfigurationParserDelegate { boolean registerAllConstructors(boolean queriedOnly, T type); + void registerUnsafeAllocated(T clazz); + String getTypeName(T type); String getSimpleName(T type); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java index 344c4a1b2af0..ba942ea40ac5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java @@ -32,6 +32,7 @@ import java.util.Map; +import com.oracle.svm.core.configure.ConfigurationFile; import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.compiler.api.replacements.Snippet; import org.graalvm.compiler.api.replacements.Snippet.ConstantParameter; @@ -69,7 +70,6 @@ import org.graalvm.compiler.replacements.SnippetTemplate.SnippetInfo; import org.graalvm.compiler.word.BarrieredAccess; import org.graalvm.compiler.word.Word; -import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.word.LocationIdentity; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; @@ -105,8 +105,6 @@ public abstract class SubstrateAllocationSnippets extends AllocationSnippets { private static final SubstrateForeignCallDescriptor ARRAY_HUB_ERROR = SnippetRuntime.findForeignCall(SubstrateAllocationSnippets.class, "arrayHubErrorStub", true); private static final SubstrateForeignCallDescriptor[] FOREIGN_CALLS = new SubstrateForeignCallDescriptor[]{NEW_MULTI_ARRAY, HUB_ERROR, ARRAY_HUB_ERROR}; - private static final String RUNTIME_REFLECTION_TYPE_NAME = RuntimeReflection.class.getTypeName(); - public static void registerForeignCalls(SubstrateForeignCallsProvider foreignCalls) { foreignCalls.register(FOREIGN_CALLS); } @@ -216,8 +214,8 @@ private static void hubErrorStub(DynamicHub hub) throws InstantiationException { } else if (!LayoutEncoding.isInstance(hub.getLayoutEncoding())) { throw new InstantiationException("Cannot allocate instance."); } else if (!hub.isInstantiated()) { - throw new IllegalArgumentException("Class " + DynamicHub.toClass(hub).getTypeName() + - " is instantiated reflectively but was never registered. Register the class by using " + RUNTIME_REFLECTION_TYPE_NAME); + throw new IllegalArgumentException("Type " + DynamicHub.toClass(hub).getTypeName() + " is instantiated reflectively but was never registered." + + " Register the type by adding \"unsafeAllocated\" for the type in " + ConfigurationFile.REFLECTION.getFileName() + "."); } else { throw VMError.shouldNotReachHere(); } @@ -250,8 +248,8 @@ private static void arrayHubErrorStub(DynamicHub elementType) { } else if (elementType == DynamicHub.fromClass(void.class)) { throw new IllegalArgumentException("Cannot allocate void array."); } else if (elementType.getArrayHub() == null || !elementType.getArrayHub().isInstantiated()) { - throw new IllegalArgumentException("Class " + DynamicHub.toClass(elementType).getTypeName() + "[]" + - " is instantiated reflectively but was never registered. Register the class by using " + RUNTIME_REFLECTION_TYPE_NAME); + throw new IllegalArgumentException("Class " + DynamicHub.toClass(elementType).getTypeName() + "[] is instantiated reflectively but was never registered." + + "Register the class by adding \"unsafeAllocated\" for the class in " + ConfigurationFile.REFLECTION.getFileName() + "."); } else { VMError.shouldNotReachHere(); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java index e44926182bb6..72ad8cde9c95 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java @@ -151,6 +151,11 @@ public boolean registerAllConstructors(boolean queriedOnly, ConditionalElement 0; } + @Override + public void registerUnsafeAllocated(ConditionalElement> clazz) { + registry.register(clazz.getCondition(), true, clazz.getElement()); + } + @Override public void registerMethod(boolean queriedOnly, ConditionalElement> type, String methodName, List>> methodParameterTypes) throws NoSuchMethodException { Class[] parameterTypesArray = getParameterTypes(methodParameterTypes); diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java index 726dd6d5e585..eaae39276aa3 100644 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java @@ -125,10 +125,12 @@ public void afterRegistration(AfterRegistrationAccess arg) { } private class JNIRuntimeAccessibilitySupportImpl extends ConditionalConfigurationRegistry implements JNIRuntimeAccess.JNIRuntimeAccessibilitySupport, ReflectionRegistry { + @Override - public void register(ConfigurationCondition condition, Class... classes) { + public void register(ConfigurationCondition condition, boolean unsafeAllocated, Class clazz) { + assert !unsafeAllocated : "unsafeAllocated can be only set via Unsafe.allocateInstance, not via JNI."; abortIfSealed(); - registerConditionalConfiguration(condition, () -> newClasses.addAll(Arrays.asList(classes))); + registerConditionalConfiguration(condition, () -> newClasses.add(clazz)); } @Override diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java index adaeeb885353..b8db6b229a7d 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java @@ -92,6 +92,7 @@ public class ReflectionDataBuilder extends ConditionalConfigurationRegistry impl private boolean sealed; private final Set> reflectionClasses = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final Set> unsafeInstantiatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final Map reflectionMethods = new ConcurrentHashMap<>(); private final Map methodAccessors = new ConcurrentHashMap<>(); private final Set reflectionFields = Collections.newSetFromMap(new ConcurrentHashMap<>()); @@ -116,17 +117,16 @@ public ReflectionDataBuilder() { } @Override - public void register(ConfigurationCondition condition, Class... classes) { + public void register(ConfigurationCondition condition, boolean unsafeInstantiated, Class clazz) { checkNotSealed(); - registerConditionalConfiguration(condition, () -> registerClasses(classes)); - } - - private void registerClasses(Class[] classes) { - for (Class clazz : classes) { + registerConditionalConfiguration(condition, () -> { + if (unsafeInstantiated) { + unsafeInstantiatedClasses.add(clazz); + } if (reflectionClasses.add(clazz)) { modifiedClasses.add(clazz); } - } + }); } @Override @@ -617,6 +617,9 @@ private void processClass(DuringAnalysisAccessImpl access, Class clazz) { * build the reflection metadata. */ type.registerAsReachable(); + if (unsafeInstantiatedClasses.contains(clazz)) { + type.registerAsAllocated(null); + } if (reflectionClasses.contains(clazz)) { ClassForNameSupport.registerClass(clazz); diff --git a/vm/mx.vm/ni-ce b/vm/mx.vm/ni-ce index df1c9ae6e437..f7cdd40b2683 100644 --- a/vm/mx.vm/ni-ce +++ b/vm/mx.vm/ni-ce @@ -1,4 +1,4 @@ DYNAMIC_IMPORTS=/substratevm DISABLE_INSTALLABLES=true EXCLUDE_COMPONENTS=pbm,pmh -NATIVE_IMAGES=native-image,lib:native-image-agent +NATIVE_IMAGES=native-image,lib:native-image-agent,lib:native-image-diagnostics-agent