Skip to content

Commit 172b382

Browse files
committed
Ensure that setScopedValueBindings can never trigger a stack overflow.
1 parent 3d8899b commit 172b382

File tree

4 files changed

+137
-10
lines changed

4 files changed

+137
-10
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateUtil.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ public static CCharPointer strchr(CCharPointer str, int c) {
212212
* are actually the same class.
213213
*/
214214
@SuppressWarnings({"unused", "unchecked"})
215+
@AlwaysInline("Some callers rely on this never becoming an actual method call.")
215216
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
216217
public static <T> T cast(Object obj, Class<T> toType) {
217218
return (T) obj;

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.graalvm.nativeimage.Platforms;
4040
import org.graalvm.nativeimage.impl.InternalPlatform;
4141

42+
import com.oracle.svm.core.AlwaysInline;
4243
import com.oracle.svm.core.SubstrateOptions;
4344
import com.oracle.svm.core.SubstrateUtil;
4445
import com.oracle.svm.core.Uninterruptible;
@@ -702,19 +703,45 @@ static void setScopedValueCache(Object[] cache) {
702703
JavaThreads.toTarget(currentCarrierThread()).scopedValueCache = cache;
703704
}
704705

706+
/**
707+
* This method is used to set and revert {@code ScopedValue} bindings as follows:
708+
*
709+
* {@code setScopedValueBindings(b); try { work(); } finally { setScopedValueBindings(previous);
710+
* }}
711+
*
712+
* If the second call fails due to a stack overflow, ScopedValue bindings leak out of their
713+
* scope. Therefore, we force-inline this method into its callers. This requires both calls to
714+
* happen in the same caller, which is the case in the usages in the JDK, and those are expected
715+
* to remain the only direct usages. {@code ScopedValue.Carrier} calls this method through the
716+
* implementation of {@code JavaLangAccess}, which is an anonymous class that we cannot
717+
* substitute to force inlining, so we substitute the calling class to invoke this method
718+
* directly in {@link Target_jdk_incubator_concurrent_ScopedValue_Carrier}.
719+
*/
705720
@Substitute
721+
@AlwaysInline("Must ensure that this can never become a call that can trigger a stack overflow and leak bindings outside the scope.")
722+
@Uninterruptible(reason = "Must not call other methods which can trigger a stack overflow.", mayBeInlined = true)
706723
@TargetElement(onlyWith = JDK20OrLater.class)
707-
static Object findScopedValueBindings() {
708-
/*
709-
* We don't have the means to extract the bindings object parameter from runWith frames on
710-
* the stack like HotSpot does. However, at this time, we need to support only two cases:
711-
* current bindings in a virtual thread, and current bindings in the carrier thread.
712-
*/
713-
Object bindings = JavaThreads.toTarget(Thread.currentThread()).scopedValueBindings;
714-
if (bindings != null) {
715-
return bindings;
724+
static void setScopedValueBindings(Object bindings) {
725+
Target_java_lang_Thread thread = SubstrateUtil.cast(PlatformThreads.currentThread.get(), Target_java_lang_Thread.class);
726+
if (LoomSupport.isEnabled() && thread.vthread != null) {
727+
thread = SubstrateUtil.cast(thread.vthread, Target_java_lang_Thread.class);
716728
}
717-
return JavaThreads.toTarget(currentCarrierThread()).scopedValueBindings;
729+
thread.scopedValueBindings = bindings;
730+
}
731+
732+
/**
733+
* On HotSpot, this method determines the correct ScopedValue bindings for the current context
734+
* by finding the top {@code runWith} invocation on the stack and extracting the bindings object
735+
* parameter from the frame. It is used following stack overflows and other situations that
736+
* could result in bindings leaking to another scope, during which {@link #scopedValueBindings}
737+
* is cleared as a precaution. We don't have the means to extract the bindings object from the
738+
* stack, but we ensure that {@link #setScopedValueBindings} does not trigger stack overflows,
739+
* so this method should never be needed.
740+
*/
741+
@Substitute
742+
@TargetElement(onlyWith = JDK20OrLater.class)
743+
static Object findScopedValueBindings() {
744+
throw VMError.shouldNotReachHere("ScopedValue bindings are never cleared.");
718745
}
719746

720747
@Substitute
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.core.thread;
26+
27+
import java.util.concurrent.Callable;
28+
import java.util.function.BooleanSupplier;
29+
30+
import org.graalvm.compiler.serviceprovider.JavaVersionUtil;
31+
import org.graalvm.nativeimage.Platform;
32+
import org.graalvm.nativeimage.Platforms;
33+
34+
import com.oracle.svm.core.annotate.Alias;
35+
import com.oracle.svm.core.annotate.Substitute;
36+
import com.oracle.svm.core.annotate.TargetClass;
37+
import com.oracle.svm.core.jdk.ModuleUtil;
38+
39+
@Platforms(Platform.HOSTED_ONLY.class)
40+
final class IncubatorConcurrentModule implements BooleanSupplier {
41+
@Override
42+
public boolean getAsBoolean() {
43+
return JavaVersionUtil.JAVA_SPEC >= 20 && ModuleUtil.bootLayerContainsModule("jdk.incubator.concurrent");
44+
}
45+
}
46+
47+
/**
48+
* Substituted to directly call {@link Target_java_lang_Thread#setScopedValueBindings} for forced
49+
* inlining.
50+
*/
51+
@TargetClass(className = "jdk.incubator.concurrent.ScopedValue", innerClass = "Carrier", onlyWith = IncubatorConcurrentModule.class)
52+
final class Target_jdk_incubator_concurrent_ScopedValue_Carrier {
53+
@Alias int bitmask;
54+
55+
@Substitute
56+
private <R> R runWith(Target_jdk_incubator_concurrent_ScopedValue_Snapshot newSnapshot, Callable<R> op) throws Exception {
57+
Target_java_lang_Thread.setScopedValueBindings(newSnapshot);
58+
try {
59+
return Target_jdk_internal_vm_ScopedValueContainer.call(op);
60+
} finally {
61+
Target_java_lang_Thread.setScopedValueBindings(newSnapshot.prev);
62+
Target_jdk_incubator_concurrent_ScopedValue_Cache.invalidate(bitmask);
63+
}
64+
}
65+
66+
@Substitute
67+
private void runWith(Target_jdk_incubator_concurrent_ScopedValue_Snapshot newSnapshot, Runnable op) {
68+
Target_java_lang_Thread.setScopedValueBindings(newSnapshot);
69+
try {
70+
Target_jdk_internal_vm_ScopedValueContainer.run(op);
71+
} finally {
72+
Target_java_lang_Thread.setScopedValueBindings(newSnapshot.prev);
73+
Target_jdk_incubator_concurrent_ScopedValue_Cache.invalidate(bitmask);
74+
}
75+
}
76+
}
77+
78+
@TargetClass(className = "jdk.internal.vm.ScopedValueContainer", onlyWith = IncubatorConcurrentModule.class)
79+
final class Target_jdk_internal_vm_ScopedValueContainer {
80+
@Alias
81+
static native <V> V call(Callable<V> op) throws Exception;
82+
83+
@Alias
84+
static native void run(Runnable op);
85+
}
86+
87+
@TargetClass(className = "jdk.incubator.concurrent.ScopedValue", innerClass = "Snapshot", onlyWith = IncubatorConcurrentModule.class)
88+
final class Target_jdk_incubator_concurrent_ScopedValue_Snapshot {
89+
@Alias //
90+
Target_jdk_incubator_concurrent_ScopedValue_Snapshot prev;
91+
}
92+
93+
@TargetClass(className = "jdk.incubator.concurrent.ScopedValue", innerClass = "Cache", onlyWith = IncubatorConcurrentModule.class)
94+
final class Target_jdk_incubator_concurrent_ScopedValue_Cache {
95+
@Alias
96+
static native void invalidate(int toClearBits);
97+
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_jdk_internal_vm_ThreadContainers.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@
4141
@TargetClass(className = "jdk.internal.vm.ThreadContainers", onlyWith = JDK19OrLater.class)
4242
@SuppressWarnings("unused")
4343
final class Target_jdk_internal_vm_ThreadContainers {
44+
// Checkstyle: stop
4445
@Delete static Set<WeakReference<Target_jdk_internal_vm_ThreadContainer>> CONTAINER_REGISTRY;
4546
@Delete static ReferenceQueue<Object> QUEUE;
47+
// Checkstyle: resume
4648

4749
@Substitute
4850
public static Object registerContainer(Target_jdk_internal_vm_ThreadContainer container) {

0 commit comments

Comments
 (0)