[vm/shared] Enforce pragma('vm:shared') annotations for captured local vars.
Fixes https://github.com/dart-lang/sdk/issues/61287 TEST=ffi/isolate_group_bound_captured_local_test Change-Id: I9dc1e8aaf9e99d8ec9ad730cf1cd89ae3b4ed148 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/444923 Commit-Queue: Alexander Aprelev <aam@google.com> Reviewed-by: Ryan Macnak <rmacnak@google.com>
This commit is contained in:
committed by
Commit Queue
parent
94894b1796
commit
b759d096cb
@@ -398,6 +398,16 @@ void FfiCallbackMetadata::EnsureOnlyTriviallyImmutableValuesInClosure(
|
||||
for (intptr_t i = 0; i < context.num_variables(); i++) {
|
||||
ValidateTriviallyImmutabilityOfAnObject(zone, &obj, context.At(i));
|
||||
}
|
||||
|
||||
if (!function.does_close_over_only_final_and_shared_vars()) {
|
||||
const String& error = String::Handle(
|
||||
zone,
|
||||
String::New(
|
||||
"Only final and 'vm:shared' variables can be captured by isolate "
|
||||
"group callbacks."));
|
||||
Exceptions::ThrowArgumentError(error);
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8586,6 +8586,27 @@ void Function::set_awaiter_link(Function::AwaiterLink link) const {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
bool Function::does_close_over_only_final_and_shared_vars() const {
|
||||
if (IsClosureFunction()) {
|
||||
const Object& obj = Object::Handle(untag()->data());
|
||||
ASSERT(!obj.IsNull());
|
||||
return ClosureData::Cast(obj).does_close_over_only_final_and_shared_vars();
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void Function::set_does_close_over_only_final_and_shared_vars(
|
||||
bool value) const {
|
||||
if (IsClosureFunction()) {
|
||||
const Object& obj = Object::Handle(untag()->data());
|
||||
ASSERT(!obj.IsNull());
|
||||
ClosureData::Cast(obj).set_does_close_over_only_final_and_shared_vars(
|
||||
value);
|
||||
return;
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
ClosurePtr Function::implicit_static_closure() const {
|
||||
if (IsImplicitStaticClosureFunction()) {
|
||||
const Object& obj = Object::Handle(untag()->data());
|
||||
@@ -12128,6 +12149,19 @@ void ClosureData::set_awaiter_link(Function::AwaiterLink link) const {
|
||||
link.index);
|
||||
}
|
||||
|
||||
bool ClosureData::does_close_over_only_final_and_shared_vars() const {
|
||||
return untag()
|
||||
->packed_fields_
|
||||
.Read<UntaggedClosureData::DoesCloseOverOnlySharedFields>();
|
||||
}
|
||||
|
||||
void ClosureData::set_does_close_over_only_final_and_shared_vars(
|
||||
bool value) const {
|
||||
untag()
|
||||
->packed_fields_
|
||||
.Update<UntaggedClosureData::DoesCloseOverOnlySharedFields>(value);
|
||||
}
|
||||
|
||||
ClosureDataPtr ClosureData::New() {
|
||||
ASSERT(Object::closure_data_class() != Class::null());
|
||||
return Object::Allocate<ClosureData>(Heap::kOld);
|
||||
|
||||
@@ -3302,6 +3302,9 @@ class Function : public Object {
|
||||
(awaiter_link().depth != UntaggedClosureData::kNoAwaiterLinkDepth);
|
||||
}
|
||||
|
||||
void set_does_close_over_only_final_and_shared_vars(bool value) const;
|
||||
bool does_close_over_only_final_and_shared_vars() const;
|
||||
|
||||
// Enclosing function of this local function.
|
||||
FunctionPtr parent_function() const;
|
||||
|
||||
@@ -4393,6 +4396,9 @@ class ClosureData : public Object {
|
||||
Function::AwaiterLink awaiter_link() const;
|
||||
void set_awaiter_link(Function::AwaiterLink link) const;
|
||||
|
||||
bool does_close_over_only_final_and_shared_vars() const;
|
||||
void set_does_close_over_only_final_and_shared_vars(bool value) const;
|
||||
|
||||
// Enclosing function of this local function.
|
||||
PRECOMPILER_WSR_FIELD_DECLARATION(Function, parent_function)
|
||||
|
||||
|
||||
@@ -1521,7 +1521,10 @@ class UntaggedClosureData : public UntaggedObject {
|
||||
using PackedAwaiterLinkIndex = BitField<decltype(packed_fields_),
|
||||
uint8_t,
|
||||
PackedAwaiterLinkDepth::kNextBit>;
|
||||
|
||||
using DoesCloseOverOnlySharedFields =
|
||||
BitField<decltype(packed_fields_),
|
||||
bool,
|
||||
PackedAwaiterLinkIndex::kNextBit>;
|
||||
friend class Function;
|
||||
friend class UnitDeserializationRoots;
|
||||
};
|
||||
@@ -2470,7 +2473,8 @@ class UntaggedContext : public UntaggedObject {
|
||||
V(Late) \
|
||||
V(Nullable) \
|
||||
V(Invisible) \
|
||||
V(AwaiterLink)
|
||||
V(AwaiterLink) \
|
||||
V(Shared)
|
||||
|
||||
class UntaggedContextScope : public UntaggedObject {
|
||||
RAW_HEAP_OBJECT_IMPLEMENTATION(ContextScope);
|
||||
|
||||
@@ -462,6 +462,8 @@ ContextScopePtr LocalScope::PreserveOuterScope(
|
||||
|
||||
LocalVariable* awaiter_link = nullptr;
|
||||
|
||||
bool does_capture_only_final_and_shared_vars = true;
|
||||
|
||||
// Create a descriptor for each referenced captured variable of enclosing
|
||||
// functions to preserve its name and its context allocation information.
|
||||
int captured_idx = 0;
|
||||
@@ -500,6 +502,13 @@ ContextScopePtr LocalScope::PreserveOuterScope(
|
||||
if (is_awaiter_link) {
|
||||
awaiter_link = variable;
|
||||
}
|
||||
|
||||
bool is_shared = variable->ComputeIfShared(library);
|
||||
context_scope.SetIsSharedAt(captured_idx, is_shared);
|
||||
if (!is_shared && !variable->is_final()) {
|
||||
does_capture_only_final_and_shared_vars = false;
|
||||
}
|
||||
|
||||
captured_idx++;
|
||||
}
|
||||
}
|
||||
@@ -523,6 +532,11 @@ ContextScopePtr LocalScope::PreserveOuterScope(
|
||||
}
|
||||
}
|
||||
|
||||
if (!function.IsNull()) {
|
||||
function.set_does_close_over_only_final_and_shared_vars(
|
||||
does_capture_only_final_and_shared_vars);
|
||||
}
|
||||
|
||||
return context_scope.ptr();
|
||||
}
|
||||
|
||||
@@ -542,6 +556,7 @@ LocalScope* LocalScope::RestoreOuterScope(const ContextScope& context_scope) {
|
||||
String::ZoneHandle(context_scope.NameAt(i)), static_type,
|
||||
context_scope.KernelOffsetAt(i), inferred_type);
|
||||
variable->set_is_awaiter_link(context_scope.IsAwaiterLinkAt(i));
|
||||
variable->set_is_shared(context_scope.IsSharedAt(i));
|
||||
variable->set_is_captured();
|
||||
variable->set_index(VariableIndex(context_scope.ContextIndexAt(i)));
|
||||
if (context_scope.IsFinalAt(i)) {
|
||||
@@ -693,6 +708,19 @@ bool LocalVariable::ComputeIfIsAwaiterLink(const Library& library) {
|
||||
return is_awaiter_link_ == IsAwaiterLink::kLink;
|
||||
}
|
||||
|
||||
bool LocalVariable::ComputeIfShared(const Library& library) {
|
||||
if (is_shared_ == IsShared::kUnknown) {
|
||||
RELEASE_ASSERT(annotations_offset_ != kNoKernelOffset);
|
||||
Thread* T = Thread::Current();
|
||||
Zone* Z = T->zone();
|
||||
const auto& metadata = Object::Handle(
|
||||
Z, kernel::EvaluateMetadata(library, annotations_offset_,
|
||||
/* is_annotations_offset = */ true));
|
||||
set_is_shared(FindPragmaInMetadata(T, metadata, Symbols::vm_shared()));
|
||||
}
|
||||
return is_shared_ == IsShared::kShared;
|
||||
}
|
||||
|
||||
bool LocalVariable::Equals(const LocalVariable& other) const {
|
||||
if (HasIndex() && other.HasIndex() && (index() == other.index())) {
|
||||
if (is_captured() == other.is_captured()) {
|
||||
|
||||
+16
-1
@@ -104,7 +104,8 @@ class LocalVariable : public ZoneAllocated {
|
||||
late_init_offset_(0),
|
||||
type_check_mode_(kDoTypeCheck),
|
||||
index_(),
|
||||
is_awaiter_link_(IsAwaiterLink::kNotLink) {
|
||||
is_awaiter_link_(IsAwaiterLink::kNotLink),
|
||||
is_shared_(IsShared::kNotShared) {
|
||||
DEBUG_ASSERT(static_type.IsNotTemporaryScopedHandle());
|
||||
ASSERT(static_type.IsFinalized());
|
||||
ASSERT(inferred_type != nullptr);
|
||||
@@ -129,6 +130,8 @@ class LocalVariable : public ZoneAllocated {
|
||||
annotations_offset_ = offset;
|
||||
is_awaiter_link_ = (offset == kNoKernelOffset) ? IsAwaiterLink::kNotLink
|
||||
: IsAwaiterLink::kUnknown;
|
||||
is_shared_ =
|
||||
(offset == kNoKernelOffset) ? IsShared::kNotShared : IsShared::kUnknown;
|
||||
}
|
||||
|
||||
const AbstractType& static_type() const { return static_type_; }
|
||||
@@ -151,6 +154,11 @@ class LocalVariable : public ZoneAllocated {
|
||||
bool is_late() const { return IsLateBit::decode(bitfield_); }
|
||||
void set_is_late() { bitfield_ = IsLateBit::update(true, bitfield_); }
|
||||
|
||||
bool ComputeIfShared(const Library& library);
|
||||
void set_is_shared(bool value) {
|
||||
is_shared_ = value ? IsShared::kShared : IsShared::kNotShared;
|
||||
}
|
||||
|
||||
intptr_t late_init_offset() const { return late_init_offset_; }
|
||||
void set_late_init_offset(intptr_t late_init_offset) {
|
||||
late_init_offset_ = late_init_offset;
|
||||
@@ -264,6 +272,13 @@ class LocalVariable : public ZoneAllocated {
|
||||
};
|
||||
IsAwaiterLink is_awaiter_link_;
|
||||
|
||||
enum class IsShared {
|
||||
kUnknown,
|
||||
kNotShared,
|
||||
kShared,
|
||||
};
|
||||
IsShared is_shared_;
|
||||
|
||||
friend class LocalScope;
|
||||
DISALLOW_COPY_AND_ASSIGN(LocalVariable);
|
||||
};
|
||||
|
||||
@@ -577,6 +577,7 @@ class ObjectPointerVisitor;
|
||||
V(vm_notify_debugger_on_exception, "vm:notify-debugger-on-exception") \
|
||||
V(vm_prefer_inline, "vm:prefer-inline") \
|
||||
V(vm_recognized, "vm:recognized") \
|
||||
V(vm_shared, "vm:shared") \
|
||||
V(vm_testing_print_flow_graph, "vm:testing:print-flow-graph") \
|
||||
V(vm_trace_entrypoints, "vm:testing.unsafe.trace-entrypoints-fn") \
|
||||
V(vm_unsafe_no_interrupts, "vm:unsafe:no-interrupts") \
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// Test that exception is thrown when captured variables doesn't have
|
||||
// pragma('vm:shared') annotation.
|
||||
//
|
||||
// VMOptions=--experimental-shared-data
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:dart_internal/isolate_group.dart' show IsolateGroup;
|
||||
|
||||
import "package:expect/async_helper.dart";
|
||||
import "package:expect/expect.dart";
|
||||
|
||||
typedef CallbackNativeType = Void Function(Int64, Int32);
|
||||
|
||||
Future<void> testCapturedLocalVarNoDecoration() async {
|
||||
int foo_result = 42;
|
||||
Expect.throws(() {
|
||||
final callback = NativeCallable<CallbackNativeType>.isolateGroupBound((
|
||||
int a,
|
||||
int b,
|
||||
) {
|
||||
foo_result += (a * b);
|
||||
});
|
||||
}, (e) => e.toString().contains("variables can be captured"));
|
||||
}
|
||||
|
||||
Future<void> testCapturedLocalVarPragmaVmShared() async {
|
||||
@pragma('vm:shared')
|
||||
int foo_result = 42;
|
||||
final callback = NativeCallable<CallbackNativeType>.isolateGroupBound((
|
||||
int a,
|
||||
int b,
|
||||
) {
|
||||
foo_result += (a * b);
|
||||
});
|
||||
callback.close();
|
||||
}
|
||||
|
||||
Future<void> testCapturedLocalVarFinal() async {
|
||||
final int foo_result = 42;
|
||||
final callback = NativeCallable<CallbackNativeType>.isolateGroupBound((
|
||||
int a,
|
||||
int b,
|
||||
) {
|
||||
foo_result + (a * b);
|
||||
});
|
||||
callback.close();
|
||||
}
|
||||
|
||||
main(args, message) async {
|
||||
asyncStart();
|
||||
await testCapturedLocalVarNoDecoration();
|
||||
await testCapturedLocalVarPragmaVmShared();
|
||||
await testCapturedLocalVarFinal();
|
||||
asyncEnd();
|
||||
print("All tests completed :)");
|
||||
}
|
||||
@@ -62,8 +62,10 @@ testInitStrings() async {
|
||||
for (int i = 0; i < nWorkers; i++) {
|
||||
Isolate.spawn(
|
||||
(sendPort) {
|
||||
@pragma('vm:shared')
|
||||
final sp = sendPort;
|
||||
IsolateGroup.runSync(() {
|
||||
sendPort.send(shared_late_final_string);
|
||||
sp.send(shared_late_final_string);
|
||||
});
|
||||
},
|
||||
rp.sendPort,
|
||||
@@ -100,9 +102,11 @@ testInitThrows() async {
|
||||
for (int i = 0; i < nWorkers; i++) {
|
||||
Isolate.spawn(
|
||||
(sendPort) {
|
||||
@pragma('vm:shared')
|
||||
final sp = sendPort;
|
||||
IsolateGroup.runSync(() {
|
||||
try {
|
||||
sendPort.send(shared_late_final_throw);
|
||||
sp.send(shared_late_final_throw);
|
||||
} catch (e) {
|
||||
Expect.equals("165", e);
|
||||
rethrow;
|
||||
|
||||
@@ -54,6 +54,7 @@ String string_foo = "";
|
||||
SendPort? sp;
|
||||
|
||||
StringMethodTearoffTest() {
|
||||
@pragma('vm:shared')
|
||||
final stringTearoff = "abc".toString;
|
||||
IsolateGroup.runSync(() {
|
||||
stringTearoff;
|
||||
|
||||
Reference in New Issue
Block a user