Skip to content

Commit f0d0195

Browse files
committed
[GR-64496] Disallow FFM API objects with associated native resources in image heap.
PullRequest: graal/20767
2 parents 8acbbf8 + 2e5fc5b commit f0d0195

File tree

9 files changed

+136
-87
lines changed

9 files changed

+136
-87
lines changed

substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsRuntime.java

+69-3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import static jdk.graal.compiler.core.common.spi.ForeignCallDescriptor.CallSideEffect.HAS_SIDE_EFFECT;
2828

2929
import java.lang.constant.DirectMethodHandleDesc;
30+
import java.lang.foreign.MemorySegment;
31+
import java.lang.foreign.MemorySegment.Scope;
3032
import java.lang.invoke.MethodHandle;
3133
import java.util.HashMap;
3234
import java.util.Locale;
@@ -42,13 +44,16 @@
4244
import org.graalvm.word.LocationIdentity;
4345
import org.graalvm.word.Pointer;
4446

47+
import com.oracle.svm.core.ForeignSupport;
4548
import com.oracle.svm.core.FunctionPointerHolder;
4649
import com.oracle.svm.core.OS;
4750
import com.oracle.svm.core.SubstrateOptions;
4851
import com.oracle.svm.core.SubstrateUtil;
4952
import com.oracle.svm.core.Uninterruptible;
53+
import com.oracle.svm.core.c.InvokeJavaFunctionPointer;
5054
import com.oracle.svm.core.headers.LibC;
5155
import com.oracle.svm.core.headers.WindowsAPIs;
56+
import com.oracle.svm.core.image.DisallowedImageHeapObjects.DisallowedObjectReporter;
5257
import com.oracle.svm.core.snippets.SnippetRuntime;
5358
import com.oracle.svm.core.snippets.SubstrateForeignCallTarget;
5459
import com.oracle.svm.core.util.BasedOnJDKFile;
@@ -57,15 +62,16 @@
5762
import jdk.graal.compiler.api.replacements.Fold;
5863
import jdk.graal.compiler.word.Word;
5964
import jdk.internal.foreign.CABI;
65+
import jdk.internal.foreign.MemorySessionImpl;
6066
import jdk.internal.foreign.abi.CapturableState;
6167

62-
public class ForeignFunctionsRuntime {
68+
public class ForeignFunctionsRuntime implements ForeignSupport {
6369
@Fold
6470
public static ForeignFunctionsRuntime singleton() {
6571
return ImageSingletons.lookup(ForeignFunctionsRuntime.class);
6672
}
6773

68-
private final AbiUtils.TrampolineTemplate trampolineTemplate = AbiUtils.singleton().generateTrampolineTemplate();
74+
private final AbiUtils.TrampolineTemplate trampolineTemplate;
6975
private final EconomicMap<NativeEntryPointInfo, FunctionPointerHolder> downcallStubs = EconomicMap.create();
7076
private final EconomicMap<DirectMethodHandleDesc, FunctionPointerHolder> directUpcallStubs = EconomicMap.create();
7177
private final EconomicMap<JavaEntryPointInfo, FunctionPointerHolder> upcallStubs = EconomicMap.create();
@@ -77,7 +83,8 @@ public static ForeignFunctionsRuntime singleton() {
7783
private BiConsumer<Long, DirectMethodHandleDesc> usingSpecializedUpcallListener;
7884

7985
@Platforms(Platform.HOSTED_ONLY.class)
80-
public ForeignFunctionsRuntime() {
86+
public ForeignFunctionsRuntime(AbiUtils abiUtils) {
87+
this.trampolineTemplate = abiUtils.generateTrampolineTemplate();
8188
}
8289

8390
public static boolean areFunctionCallsSupported() {
@@ -224,6 +231,60 @@ private static String generateMessage(JavaEntryPointInfo jep) {
224231
}
225232
}
226233

234+
/**
235+
* Arguments follow the same structure as described in {@link NativeEntryPointInfo}, with an
236+
* additional {@link Target_jdk_internal_foreign_abi_NativeEntryPoint} (NEP) as the last
237+
* argument, i.e.
238+
*
239+
* <pre>
240+
* {@code
241+
* [return buffer address] <call address> [capture state address] <actual arg 1> <actual arg 2> ... <NEP>
242+
* }
243+
* </pre>
244+
*
245+
* where <actual arg i>s are the arguments which end up being passed to the C native function
246+
*/
247+
@Override
248+
public Object linkToNative(Object... args) throws Throwable {
249+
Target_jdk_internal_foreign_abi_NativeEntryPoint nep = (Target_jdk_internal_foreign_abi_NativeEntryPoint) args[args.length - 1];
250+
StubPointer pointer = Word.pointer(nep.downcallStubAddress);
251+
/* The nep argument will be dropped in the invoked function */
252+
return pointer.invoke(args);
253+
}
254+
255+
@Override
256+
public void onMemorySegmentReachable(Object memorySegmentObj, DisallowedObjectReporter reporter) {
257+
VMError.guarantee(memorySegmentObj instanceof MemorySegment);
258+
259+
MemorySegment memorySegment = (MemorySegment) memorySegmentObj;
260+
if (memorySegment.isNative() && !MemorySegment.NULL.equals(memorySegment)) {
261+
throw reporter.raise("Detected a native MemorySegment in the image heap. " +
262+
"A native MemorySegment has a pointer to unmanaged C memory, and C memory from the image generator is not available at image runtime.", memorySegment,
263+
"Try avoiding to initialize the class that called 'MemorySegment.ofAddress'.");
264+
}
265+
}
266+
267+
@Override
268+
@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+21/src/java.base/share/classes/java/lang/foreign/MemorySegment.java#L2708")
269+
public void onScopeReachable(Object scopeObj, DisallowedObjectReporter reporter) {
270+
VMError.guarantee(scopeObj instanceof Scope);
271+
272+
/*
273+
* We never allow memory sessions with state 'OPEN' to be included in the image heap because
274+
* native memory may be associated with them which will be attempted to be free'd if the
275+
* session is closed. Non-closable or closed sessions are allowed.
276+
*
277+
* Note: This assumes that there is only one implementor of interface Scope which is
278+
* MemorySessionImpl. If JDK's class hierarchy changes, we need to adapt this as well.
279+
*/
280+
if (scopeObj instanceof MemorySessionImpl memorySessionImpl && memorySessionImpl.isAlive() && memorySessionImpl.isCloseable()) {
281+
throw reporter.raise("Detected an open but closable MemorySegment.Scope in the image heap. " +
282+
"A MemorySegment.Scope may have associated unmanaged C memory that will be attempted to be free'd if the scope is closed. " +
283+
"However, C memory from the image generator is no longer available at image runtime.", memorySessionImpl,
284+
"Try avoiding to initialize the class that called 'Arena.ofConfined/ofShared'.");
285+
}
286+
}
287+
227288
/**
228289
* Workaround for CapturableState.mask() being interruptible.
229290
*/
@@ -276,3 +337,8 @@ public static void captureCallState(int statesToCapture, CIntPointer captureBuff
276337
public static final SnippetRuntime.SubstrateForeignCallDescriptor CAPTURE_CALL_STATE = SnippetRuntime.findForeignCall(ForeignFunctionsRuntime.class,
277338
"captureCallState", HAS_SIDE_EFFECT, LocationIdentity.any());
278339
}
340+
341+
interface StubPointer extends CFunctionPointer {
342+
@InvokeJavaFunctionPointer
343+
Object invoke(Object... args);
344+
}

substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/LinkToNativeSupportImpl.java

-59
This file was deleted.

substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_MemorySessionImpl.java

+11-2
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,22 @@
2828
import com.oracle.svm.core.annotate.RecomputeFieldValue;
2929
import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind;
3030
import com.oracle.svm.core.annotate.TargetClass;
31-
import com.oracle.svm.core.jdk.JDKLatest;
3231

33-
@TargetClass(className = "jdk.internal.foreign.MemorySessionImpl", onlyWith = {JDKLatest.class, ForeignAPIPredicates.Enabled.class})
32+
import jdk.internal.foreign.MemorySessionImpl.ResourceList;
33+
34+
@TargetClass(className = "jdk.internal.foreign.MemorySessionImpl", onlyWith = ForeignAPIPredicates.Enabled.class)
3435
final class Target_jdk_internal_foreign_MemorySessionImpl {
3536
@Alias //
3637
int state;
3738

39+
/**
40+
* Non-closable or closed memory sessions may land in the image heap but the resource list is no
41+
* longer required.
42+
*/
43+
@Alias //
44+
@RecomputeFieldValue(isFinal = true, kind = Kind.Reset) //
45+
ResourceList resourceList;
46+
3847
@Alias //
3948
@RecomputeFieldValue(isFinal = true, kind = Kind.None) //
4049
static int CLOSED;

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/LinkToNativeSupport.java renamed to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/ForeignSupport.java

+14-5
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,28 @@
2424
*/
2525
package com.oracle.svm.core;
2626

27-
import jdk.graal.compiler.api.replacements.Fold;
2827
import org.graalvm.nativeimage.ImageSingletons;
2928

30-
public interface LinkToNativeSupport {
29+
import com.oracle.svm.core.image.DisallowedImageHeapObjects.DisallowedObjectReporter;
30+
31+
import jdk.graal.compiler.api.replacements.Fold;
32+
33+
public interface ForeignSupport {
3134
@Fold
3235
static boolean isAvailable() {
33-
return ImageSingletons.contains(LinkToNativeSupport.class);
36+
boolean result = ImageSingletons.contains(ForeignSupport.class);
37+
assert result || !SubstrateOptions.ForeignAPISupport.getValue();
38+
return result;
3439
}
3540

3641
@Fold
37-
static LinkToNativeSupport singleton() {
38-
return ImageSingletons.lookup(LinkToNativeSupport.class);
42+
static ForeignSupport singleton() {
43+
return ImageSingletons.lookup(ForeignSupport.class);
3944
}
4045

4146
Object linkToNative(Object... args) throws Throwable;
47+
48+
void onMemorySegmentReachable(Object obj, DisallowedObjectReporter reporter);
49+
50+
void onScopeReachable(Object obj, DisallowedObjectReporter reporter);
4251
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/DisallowedImageHeapObjects.java

+16-5
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.util.random.RandomGenerator;
3939
import java.util.zip.ZipFile;
4040

41+
import com.oracle.svm.core.ForeignSupport;
4142
import com.oracle.svm.core.SubstrateUtil;
4243
import com.oracle.svm.core.util.VMError;
4344
import com.oracle.svm.util.ReflectionUtil;
@@ -53,12 +54,14 @@ public interface DisallowedObjectReporter {
5354
RuntimeException raise(String msg, Object obj, String initializerAction);
5455
}
5556

56-
public static final Class<?> CANCELLABLE_CLASS = ReflectionUtil.lookupClass(false, "sun.nio.fs.Cancellable");
57-
private static final Class<?> VIRTUAL_THREAD_CLASS = ReflectionUtil.lookupClass(false, "java.lang.VirtualThread");
58-
public static final Class<?> CONTINUATION_CLASS = ReflectionUtil.lookupClass(false, "jdk.internal.vm.Continuation");
57+
public static final Class<?> CANCELLABLE_CLASS = ReflectionUtil.lookupClass("sun.nio.fs.Cancellable");
58+
private static final Class<?> VIRTUAL_THREAD_CLASS = ReflectionUtil.lookupClass("java.lang.VirtualThread");
59+
public static final Class<?> CONTINUATION_CLASS = ReflectionUtil.lookupClass("jdk.internal.vm.Continuation");
5960
private static final Method CONTINUATION_IS_STARTED_METHOD = ReflectionUtil.lookupMethod(CONTINUATION_CLASS, "isStarted");
60-
private static final Class<?> CLEANER_CLEANABLE_CLASS = ReflectionUtil.lookupClass(false, "jdk.internal.ref.CleanerImpl$CleanerCleanable");
61-
public static final Class<?> LEGACY_CLEANER_CLASS = ReflectionUtil.lookupClass(false, "jdk.internal.ref.Cleaner");
61+
private static final Class<?> CLEANER_CLEANABLE_CLASS = ReflectionUtil.lookupClass("jdk.internal.ref.CleanerImpl$CleanerCleanable");
62+
public static final Class<?> LEGACY_CLEANER_CLASS = ReflectionUtil.lookupClass("jdk.internal.ref.Cleaner");
63+
public static final Class<?> MEMORY_SEGMENT_CLASS = ReflectionUtil.lookupClass("java.lang.foreign.MemorySegment");
64+
public static final Class<?> SCOPE_CLASS = ReflectionUtil.lookupClass("java.lang.foreign.MemorySegment$Scope");
6265

6366
public static void check(Object obj, DisallowedObjectReporter reporter) {
6467
if (obj instanceof SplittableRandom random) {
@@ -98,6 +101,14 @@ public static void check(Object obj, DisallowedObjectReporter reporter) {
98101
if (CANCELLABLE_CLASS.isInstance(obj)) {
99102
onCancellableReachable(obj, reporter);
100103
}
104+
105+
if (MEMORY_SEGMENT_CLASS.isInstance(obj) && ForeignSupport.isAvailable()) {
106+
ForeignSupport.singleton().onMemorySegmentReachable(obj, reporter);
107+
}
108+
109+
if (SCOPE_CLASS.isInstance(obj) && ForeignSupport.isAvailable()) {
110+
ForeignSupport.singleton().onScopeReachable(obj, reporter);
111+
}
101112
}
102113

103114
public static void onRandomReachable(Random random, DisallowedObjectReporter reporter) {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
import java.lang.reflect.Modifier;
3838
import java.util.Arrays;
3939

40-
import com.oracle.svm.core.LinkToNativeSupport;
40+
import com.oracle.svm.core.ForeignSupport;
4141
import com.oracle.svm.core.SubstrateUtil;
4242
import com.oracle.svm.core.annotate.Alias;
4343
import com.oracle.svm.core.annotate.Delete;
@@ -144,8 +144,8 @@ static Object linkToSpecial(Object... args) throws Throwable {
144144

145145
@Substitute(polymorphicSignature = true)
146146
static Object linkToNative(Object... args) throws Throwable {
147-
if (LinkToNativeSupport.isAvailable()) {
148-
return LinkToNativeSupport.singleton().linkToNative(args);
147+
if (ForeignSupport.isAvailable()) {
148+
return ForeignSupport.singleton().linkToNative(args);
149149
} else {
150150
throw unsupportedFeature("The foreign downcalls feature is not available. Please make sure that preview features are enabled with '--enable-preview'.");
151151
}

substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/DowncallStub.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
import com.oracle.svm.core.foreign.AbiUtils;
3636
import com.oracle.svm.core.foreign.DowncallStubsHolder;
3737
import com.oracle.svm.core.foreign.ForeignFunctionsRuntime;
38-
import com.oracle.svm.core.foreign.LinkToNativeSupportImpl;
3938
import com.oracle.svm.core.foreign.NativeEntryPointInfo;
4039
import com.oracle.svm.core.foreign.Target_jdk_internal_foreign_abi_NativeEntryPoint;
4140
import com.oracle.svm.core.graal.code.AssignedLocation;
@@ -65,7 +64,7 @@
6564
* float, double, pointer) which fit in a register --- done by HotSpot's implementation using method
6665
* handles (or specialized classes);</li>
6766
* <li>Unbox the arguments (the arguments are in an array of Objects, due to funneling through
68-
* {@link LinkToNativeSupportImpl#linkToNative}) --- done by
67+
* {@link ForeignFunctionsRuntime#linkToNative}) --- done by
6968
* {@link ForeignGraphKit#unboxArguments};</li>
7069
* <li>Further adapt arguments as to satisfy SubstrateVM's backends --- done by
7170
* {@link AbiUtils.adapt}</li>
@@ -128,7 +127,7 @@ public AnnotationValue[] getInjectedAnnotations() {
128127

129128
/**
130129
* The arguments follow the structure described in
131-
* {@link LinkToNativeSupportImpl#linkToNative(Object...)}.
130+
* {@link ForeignFunctionsRuntime#linkToNative(Object...)}.
132131
*/
133132
@Override
134133
public StructuredGraph buildGraph(DebugContext debug, AnalysisMethod method, HostedProviders providers, Purpose purpose) {

substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsFeature.java

+11-6
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
import com.oracle.graal.pointsto.meta.AnalysisUniverse;
6868
import com.oracle.svm.configure.ConfigurationFile;
6969
import com.oracle.svm.configure.ConfigurationParser;
70-
import com.oracle.svm.core.LinkToNativeSupport;
70+
import com.oracle.svm.core.ForeignSupport;
7171
import com.oracle.svm.core.OS;
7272
import com.oracle.svm.core.SubstrateOptions;
7373
import com.oracle.svm.core.SubstrateUtil;
@@ -78,7 +78,6 @@
7878
import com.oracle.svm.core.foreign.AbiUtils;
7979
import com.oracle.svm.core.foreign.ForeignFunctionsRuntime;
8080
import com.oracle.svm.core.foreign.JavaEntryPointInfo;
81-
import com.oracle.svm.core.foreign.LinkToNativeSupportImpl;
8281
import com.oracle.svm.core.foreign.NativeEntryPointInfo;
8382
import com.oracle.svm.core.foreign.RuntimeSystemLookup;
8483
import com.oracle.svm.core.foreign.SubstrateMappedMemoryUtils;
@@ -280,13 +279,19 @@ public boolean isInConfiguration(IsInConfigurationAccess access) {
280279
}
281280

282281
@Override
283-
public void duringSetup(DuringSetupAccess a) {
284-
var access = (FeatureImpl.DuringSetupAccessImpl) a;
282+
public void afterRegistration(AfterRegistrationAccess access) {
285283
AbiUtils abiUtils = AbiUtils.create();
284+
ForeignFunctionsRuntime foreignFunctionsRuntime = new ForeignFunctionsRuntime(abiUtils);
285+
286286
ImageSingletons.add(AbiUtils.class, abiUtils);
287-
ImageSingletons.add(ForeignFunctionsRuntime.class, new ForeignFunctionsRuntime());
287+
ImageSingletons.add(ForeignSupport.class, foreignFunctionsRuntime);
288+
ImageSingletons.add(ForeignFunctionsRuntime.class, foreignFunctionsRuntime);
289+
}
290+
291+
@Override
292+
public void duringSetup(DuringSetupAccess a) {
293+
var access = (FeatureImpl.DuringSetupAccessImpl) a;
288294
ImageSingletons.add(RuntimeForeignAccessSupport.class, accessSupport);
289-
ImageSingletons.add(LinkToNativeSupport.class, new LinkToNativeSupportImpl());
290295
ImageSingletons.add(SharedArenaSupport.class, new SharedArenaSupportImpl());
291296

292297
ImageClassLoader imageClassLoader = access.getImageClassLoader();

0 commit comments

Comments
 (0)