Skip to content

Commit 650ed9c

Browse files
aamCommit Queue
authored andcommitted
[vm/shared] Ensure context captured by isolategroup callbacks has only trivially shareable objects.
Fixes #61210 TEST=run_isolate_group_run_test, isolate_group_bound_callback_test Change-Id: I9633726055007255f67eb9744186fdbe21fa832d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/443639 Commit-Queue: Alexander Aprelev <[email protected]> Reviewed-by: Ryan Macnak <[email protected]>
1 parent 5a61129 commit 650ed9c

File tree

6 files changed

+170
-63
lines changed

6 files changed

+170
-63
lines changed

runtime/lib/concurrent.cc

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
#include "include/dart_api.h"
66
#include "vm/bootstrap_natives.h"
7+
#include "vm/dart_api_impl.h"
8+
#include "vm/ffi_callback_metadata.h"
79
#include "vm/heap/safepoint.h"
810
#include "vm/os_thread.h"
911

@@ -120,9 +122,15 @@ DEFINE_FFI_NATIVE_ENTRY(IsolateGroup_runSync,
120122
"Encountered shared data api when functionality is disabled. "
121123
"Pass --experimental-shared-data");
122124
}
123-
124125
Thread* current_thread = Thread::Current();
125126
ASSERT(current_thread->execution_state() == Thread::kThreadInNative);
127+
128+
{
129+
DARTSCOPE(current_thread);
130+
FfiCallbackMetadata::EnsureOnlyTriviallyImmutableValuesInClosure(
131+
current_thread->zone(), Closure::RawCast(Api::UnwrapHandle(closure)));
132+
}
133+
126134
Isolate* saved_isolate = current_thread->isolate();
127135
current_thread->ExitSafepointFromNative();
128136
current_thread->set_execution_state(Thread::kThreadInVM);

runtime/vm/ffi_callback_metadata.cc

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,52 @@ PersistentHandle* FfiCallbackMetadata::CreatePersistentHandle(
351351
return handle;
352352
}
353353

354+
static void ValidateTriviallyImmutabilityOfAnObject(Zone* zone,
355+
Object* p_obj,
356+
ObjectPtr object_ptr) {
357+
*p_obj = object_ptr;
358+
if (p_obj->IsSmi() || p_obj->IsNull()) {
359+
return;
360+
}
361+
if (p_obj->IsClosure()) {
362+
FfiCallbackMetadata::EnsureOnlyTriviallyImmutableValuesInClosure(
363+
zone, Closure::RawCast(p_obj->ptr()));
364+
return;
365+
}
366+
if (!p_obj->IsImmutable()) {
367+
const String& error = String::Handle(
368+
zone,
369+
String::NewFormatted("Only trivially-immutable values are allowed: %s.",
370+
p_obj->ToCString()));
371+
Exceptions::ThrowArgumentError(error);
372+
UNREACHABLE();
373+
}
374+
}
375+
376+
void FfiCallbackMetadata::EnsureOnlyTriviallyImmutableValuesInClosure(
377+
Zone* zone,
378+
ClosurePtr closure_ptr) {
379+
Closure& closure = Closure::Handle(zone, closure_ptr);
380+
if (closure.IsNull()) {
381+
return;
382+
}
383+
Object& obj = Object::Handle(zone);
384+
const auto& function = Function::Handle(closure.function());
385+
if (function.IsImplicitClosureFunction()) {
386+
ValidateTriviallyImmutabilityOfAnObject(
387+
zone, &obj, closure.GetImplicitClosureReceiver());
388+
} else {
389+
const Context& context = Context::Handle(zone, closure.GetContext());
390+
if (context.IsNull()) {
391+
return;
392+
}
393+
// Iterate through all elements of the context.
394+
for (intptr_t i = 0; i < context.num_variables(); i++) {
395+
ValidateTriviallyImmutabilityOfAnObject(zone, &obj, context.At(i));
396+
}
397+
}
398+
}
399+
354400
FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateLocalFfiCallback(
355401
Isolate* isolate,
356402
IsolateGroup* isolate_group,
@@ -375,6 +421,12 @@ FfiCallbackMetadata::Trampoline FfiCallbackMetadata::CreateLocalFfiCallback(
375421
(isolate == nullptr && isolate_group != nullptr &&
376422
function.GetFfiCallbackKind() ==
377423
FfiCallbackKind::kIsolateGroupBoundClosureCallback));
424+
425+
if (function.GetFfiCallbackKind() ==
426+
FfiCallbackKind::kIsolateGroupBoundClosureCallback) {
427+
EnsureOnlyTriviallyImmutableValuesInClosure(zone, closure.ptr());
428+
}
429+
378430
handle = CreatePersistentHandle(
379431
isolate != nullptr ? isolate->group() : isolate_group, closure);
380432
}

runtime/vm/ffi_callback_metadata.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,10 @@ class FfiCallbackMetadata {
346346
#error What architecture?
347347
#endif
348348

349+
static void EnsureOnlyTriviallyImmutableValuesInClosure(
350+
Zone* zone,
351+
ClosurePtr closure_ptr);
352+
349353
// Visible for testing.
350354
MetadataEntry* MetadataEntryOfTrampoline(Trampoline trampoline) const;
351355
Trampoline TrampolineOfMetadataEntry(MetadataEntry* metadata) const;

tests/ffi/isolate_group_bound_callback_test.dart

Lines changed: 57 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -50,29 +50,24 @@ DynamicLibrary dlopenPlatformSpecific(String name) {
5050
return DynamicLibrary.open('$_dylibPrefix$name$_dylibExtension');
5151
}
5252

53-
class NativeLibrary {
54-
late final FnRunnerType callFunctionOnSameThread;
55-
late final FnRunnerType callFunctionOnNewThreadBlocking;
56-
late final FnRunnerType callFunctionOnNewThreadNonBlocking;
57-
late final TwoIntFnType callTwoIntFunction;
58-
late final FnSleepType sleep;
59-
60-
NativeLibrary(DynamicLibrary ffiTestFunctions) {
61-
callFunctionOnNewThreadNonBlocking = ffiTestFunctions
62-
.lookupFunction<FnRunnerNativeType, FnRunnerType>(
63-
"CallFunctionOnNewThreadNonBlocking",
64-
);
65-
callFunctionOnNewThreadBlocking = ffiTestFunctions
66-
.lookupFunction<FnRunnerNativeType, FnRunnerType>(
67-
"CallFunctionOnNewThreadBlocking",
68-
);
69-
callTwoIntFunction = ffiTestFunctions
70-
.lookupFunction<TwoIntFnNativeType, TwoIntFnType>("CallTwoIntFunction");
71-
sleep = ffiTestFunctions.lookupFunction<FnSleepNativeType, FnSleepType>(
72-
"SleepFor",
53+
DynamicLibrary get ffiTestFunctions =>
54+
dlopenPlatformSpecific("ffi_test_functions");
55+
56+
FnRunnerType get callFunctionOnNewThreadNonBlocking =>
57+
ffiTestFunctions.lookupFunction<FnRunnerNativeType, FnRunnerType>(
58+
"CallFunctionOnNewThreadNonBlocking",
7359
);
74-
}
75-
}
60+
61+
FnRunnerType get callFunctionOnNewThreadBlocking =>
62+
ffiTestFunctions.lookupFunction<FnRunnerNativeType, FnRunnerType>(
63+
"CallFunctionOnNewThreadBlocking",
64+
);
65+
66+
TwoIntFnType get callTwoIntFunction => ffiTestFunctions
67+
.lookupFunction<TwoIntFnNativeType, TwoIntFnType>("CallTwoIntFunction");
68+
69+
FnSleepType get sleep =>
70+
ffiTestFunctions.lookupFunction<FnSleepNativeType, FnSleepType>("SleepFor");
7671

7772
@pragma('vm:shared')
7873
late Mutex mutexCondvar;
@@ -88,16 +83,14 @@ const int sleepForMs = 1000;
8883

8984
void simpleFunction(int a, int b) {
9085
result += (a * b);
91-
final ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
92-
final lib = NativeLibrary(ffiTestFunctions);
93-
lib.sleep(sleepForMs);
86+
sleep(sleepForMs);
9487
mutexCondvar.runLocked(() {
9588
resultIsReady = true;
9689
conditionVariable.notify();
9790
});
9891
}
9992

100-
Future<void> testNativeCallableHelloWorld(NativeLibrary lib) async {
93+
Future<void> testNativeCallableHelloWorld() async {
10194
mutexCondvar = Mutex();
10295
conditionVariable = ConditionVariable();
10396
final callback = NativeCallable<CallbackNativeType>.isolateGroupBound(
@@ -106,7 +99,7 @@ Future<void> testNativeCallableHelloWorld(NativeLibrary lib) async {
10699

107100
result = 42;
108101
resultIsReady = false;
109-
lib.callFunctionOnNewThreadNonBlocking(1001, callback.nativeFunction);
102+
callFunctionOnNewThreadNonBlocking(1001, callback.nativeFunction);
110103

111104
mutexCondvar.runLocked(() {
112105
while (!resultIsReady) {
@@ -118,7 +111,7 @@ Future<void> testNativeCallableHelloWorld(NativeLibrary lib) async {
118111
Expect.equals(42 + (1001 * 123), result);
119112

120113
resultIsReady = false;
121-
lib.callFunctionOnNewThreadNonBlocking(1001, callback.nativeFunction);
114+
callFunctionOnNewThreadNonBlocking(1001, callback.nativeFunction);
122115
mutexCondvar.runLocked(() {
123116
while (!resultIsReady) {
124117
conditionVariable.wait(mutexCondvar, 10 * sleepForMs);
@@ -134,7 +127,7 @@ void simpleFunctionThatThrows(int a, int b) {
134127
throw 'hello, world';
135128
}
136129

137-
Future<void> testNativeCallableThrows(NativeLibrary lib) async {
130+
Future<void> testNativeCallableThrows() async {
138131
mutexCondvar = Mutex();
139132
conditionVariable = ConditionVariable();
140133
final callback = NativeCallable<CallbackNativeType>.isolateGroupBound(
@@ -147,7 +140,7 @@ Future<void> testNativeCallableThrows(NativeLibrary lib) async {
147140
// race between invoking the callback and closing it few lines down below.
148141
// So the main thing this test checks is condition variable timeout,
149142
// which is still valuable.
150-
lib.callFunctionOnNewThreadBlocking(1001, callback.nativeFunction);
143+
callFunctionOnNewThreadBlocking(1001, callback.nativeFunction);
151144

152145
mutexCondvar.runLocked(() {
153146
// Just have short one second sleep - the condition variable is not
@@ -158,15 +151,32 @@ Future<void> testNativeCallableThrows(NativeLibrary lib) async {
158151
callback.close();
159152
}
160153

161-
Future<void> testNativeCallableHelloWorldClosure(NativeLibrary lib) async {
154+
@pragma('vm:shared')
155+
SendPort? sp;
156+
157+
Future<void> testFailToCaptureReceivePort() async {
158+
final rp = ReceivePort();
159+
Expect.throws(
160+
() {
161+
NativeCallable<CallbackNativeType>.isolateGroupBound((int a, int b) {
162+
sp = rp.sendPort;
163+
});
164+
},
165+
(e) =>
166+
e is ArgumentError && e.toString().contains('Only trivially-immutable'),
167+
);
168+
rp.close();
169+
}
170+
171+
Future<void> testNativeCallableHelloWorldClosure() async {
162172
mutexCondvar = Mutex();
163173
conditionVariable = ConditionVariable();
164174
final callback = NativeCallable<CallbackNativeType>.isolateGroupBound((
165175
int a,
166176
int b,
167177
) {
168178
result += (a * b);
169-
lib.sleep(sleepForMs);
179+
sleep(sleepForMs);
170180
mutexCondvar.runLocked(() {
171181
resultIsReady = true;
172182
conditionVariable.notify();
@@ -175,7 +185,7 @@ Future<void> testNativeCallableHelloWorldClosure(NativeLibrary lib) async {
175185

176186
result = 42;
177187
resultIsReady = false;
178-
lib.callFunctionOnNewThreadNonBlocking(1001, callback.nativeFunction);
188+
callFunctionOnNewThreadNonBlocking(1001, callback.nativeFunction);
179189

180190
mutexCondvar.runLocked(() {
181191
while (!resultIsReady) {
@@ -186,7 +196,7 @@ Future<void> testNativeCallableHelloWorldClosure(NativeLibrary lib) async {
186196
Expect.equals(42 + (1001 * 123), result);
187197

188198
resultIsReady = false;
189-
lib.callFunctionOnNewThreadNonBlocking(1001, callback.nativeFunction);
199+
callFunctionOnNewThreadNonBlocking(1001, callback.nativeFunction);
190200
mutexCondvar.runLocked(() {
191201
while (!resultIsReady) {
192202
conditionVariable.wait(mutexCondvar);
@@ -196,7 +206,7 @@ Future<void> testNativeCallableHelloWorldClosure(NativeLibrary lib) async {
196206
callback.close();
197207
}
198208

199-
void testNativeCallableSync(NativeLibrary lib) {
209+
void testNativeCallableSync() {
200210
final callback =
201211
NativeCallable<CallbackReturningIntNativeType>.isolateGroupBound((
202212
int a,
@@ -205,14 +215,11 @@ void testNativeCallableSync(NativeLibrary lib) {
205215
return a + b;
206216
}, exceptionalReturn: 1111);
207217

208-
Expect.equals(
209-
1234,
210-
lib.callTwoIntFunction(callback.nativeFunction, 1000, 234),
211-
);
218+
Expect.equals(1234, callTwoIntFunction(callback.nativeFunction, 1000, 234));
212219
callback.close();
213220
}
214221

215-
void testNativeCallableSyncThrows(NativeLibrary lib) {
222+
void testNativeCallableSyncThrows() {
216223
final callback =
217224
NativeCallable<CallbackReturningIntNativeType>.isolateGroupBound(
218225
(int a, int b) {
@@ -222,16 +229,13 @@ void testNativeCallableSyncThrows(NativeLibrary lib) {
222229
exceptionalReturn: 1111,
223230
);
224231

225-
Expect.equals(
226-
1111,
227-
lib.callTwoIntFunction(callback.nativeFunction, 1000, 234),
228-
);
232+
Expect.equals(1111, callTwoIntFunction(callback.nativeFunction, 1000, 234));
229233
callback.close();
230234
}
231235

232236
int isolateVar = 10;
233237

234-
void testNativeCallableAccessNonSharedVar(NativeLibrary lib) {
238+
void testNativeCallableAccessNonSharedVar() {
235239
final callback =
236240
NativeCallable<CallbackReturningIntNativeType>.isolateGroupBound((
237241
int a,
@@ -241,10 +245,7 @@ void testNativeCallableAccessNonSharedVar(NativeLibrary lib) {
241245
}, exceptionalReturn: 1111);
242246

243247
isolateVar = 42;
244-
Expect.equals(
245-
1111,
246-
lib.callTwoIntFunction(callback.nativeFunction, 1000, 234),
247-
);
248+
Expect.equals(1111, callTwoIntFunction(callback.nativeFunction, 1000, 234));
248249
callback.close();
249250
}
250251

@@ -304,14 +305,13 @@ Future<void> testKeepIsolateAliveFalse() async {
304305
main(args, message) async {
305306
asyncStart();
306307
// Simple tests.
307-
final ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
308-
final lib = NativeLibrary(ffiTestFunctions);
309-
await testNativeCallableHelloWorld(lib);
310-
await testNativeCallableThrows(lib);
311-
await testNativeCallableHelloWorldClosure(lib);
312-
testNativeCallableSync(lib);
313-
testNativeCallableSyncThrows(lib);
314-
testNativeCallableAccessNonSharedVar(lib);
308+
await testNativeCallableHelloWorld();
309+
await testNativeCallableThrows();
310+
await testFailToCaptureReceivePort();
311+
await testNativeCallableHelloWorldClosure();
312+
testNativeCallableSync();
313+
testNativeCallableSyncThrows();
314+
testNativeCallableAccessNonSharedVar();
315315
await testKeepIsolateAliveTrue();
316316
await testKeepIsolateAliveFalse();
317317
asyncEnd();

tests/ffi/isolate_group_bound_send_test.dart

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,15 @@ import "package:expect/expect.dart";
2121
main() async {
2222
asyncStart();
2323
ReceivePort rp = ReceivePort();
24-
IsolateGroup.runSync(() {
25-
rp.sendPort.send("hello");
26-
});
27-
Expect.equals("hello", await rp.first);
24+
Expect.throws(
25+
() {
26+
IsolateGroup.runSync(() {
27+
rp.sendPort.send("hello");
28+
});
29+
},
30+
(e) =>
31+
e is ArgumentError && e.toString().contains('Only trivially-immutable'),
32+
);
2833
rp.close();
2934
asyncEnd();
3035
}

0 commit comments

Comments
 (0)