mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 08:24:39 +00:00
[vm/isolates] Introduce 'vm:isolate-unsendable' pragma.
Decorate Zone, Future, Completer, Timer and Stream with newly-introduced pragma to make sure that message verification stops earlier, produces shorter retaining path for the user. BUG=https://github.com/dart-lang/sdk/issues/51722 TEST=send_unsupported_objects_test,isolate_exit_unsendable_test.dart CoreLibraryReviewExempt: vm-specific pragmas Change-Id: I499eea542d228ac9cf0797a682664f93f360dc80 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/289027 Commit-Queue: Alexander Aprelev <aam@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
parent
5b510210db
commit
67683c3973
|
@ -15,6 +15,7 @@ These pragmas are part of the VM's API and are safe for use in external code.
|
|||
| `vm:always-consider-inlining` | Marks a function which particularly benefits from inlining and specialization in context of the caller (for example, when concrete types of arguments are known). Inliner will not give up after one failed inlining attempt and will continue trying to inline this function. |
|
||||
| `vm:platform-const` | Marks a static getter or a static field with an initializer where the getter body or field initializer evaluates to a constant value if the target operating system is known. |
|
||||
| `weak-tearoff-reference` | [Declaring a static weak reference intrinsic method.](compiler/pragmas_recognized_by_compiler.md#declaring-a-static-weak-reference-intrinsic-method) |
|
||||
| `vm:isolate-unsendable` | Marks a class, instances of which won't be allowed to be passed through ports or sent between isolates. |
|
||||
|
||||
## Unsafe pragmas for general use
|
||||
|
||||
|
|
|
@ -250,18 +250,13 @@ static ObjectPtr ValidateMessageObject(Zone* zone,
|
|||
MESSAGE_SNAPSHOT_ILLEGAL(SuspendState);
|
||||
|
||||
default:
|
||||
if (cid >= kNumPredefinedCids) {
|
||||
klass = class_table->At(cid);
|
||||
if (klass.num_native_fields() != 0) {
|
||||
illegal_object = raw;
|
||||
exception_message = "is a NativeWrapper";
|
||||
break;
|
||||
}
|
||||
if (klass.implements_finalizable()) {
|
||||
illegal_object = raw;
|
||||
exception_message = "is a Finalizable";
|
||||
break;
|
||||
}
|
||||
klass = class_table->At(cid);
|
||||
if (klass.is_isolate_unsendable()) {
|
||||
illegal_object = raw;
|
||||
exception_message =
|
||||
"is unsendable object (see restrictions listed at"
|
||||
"`SendPort.send()` documentation for more information)";
|
||||
break;
|
||||
}
|
||||
}
|
||||
raw->untag()->VisitPointers(&visitor);
|
||||
|
|
47
runtime/tests/vm/dart/isolate_exit_unsendable_test.dart
Normal file
47
runtime/tests/vm/dart/isolate_exit_unsendable_test.dart
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) 2023, 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.
|
||||
//
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:async_helper/async_helper.dart';
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
@pragma('vm:isolate-unsendable')
|
||||
class Locked {}
|
||||
|
||||
class ExtendsLocked extends Locked {}
|
||||
|
||||
class ImplementsLocked implements Locked {}
|
||||
|
||||
main() async {
|
||||
asyncStart();
|
||||
final rpExit = ReceivePort();
|
||||
final rpError = RawReceivePort((e) {
|
||||
Expect.fail('Spawned isolated failed with $e');
|
||||
});
|
||||
|
||||
final rp = RawReceivePort((e) {
|
||||
Expect.fail('Received unexpected $e, no objects should have arrived');
|
||||
});
|
||||
await Isolate.spawn((sendPort) {
|
||||
for (final pairFunctionName in [
|
||||
[Locked.new, "Locked"],
|
||||
[ExtendsLocked.new, "ExtendsLocked"],
|
||||
[ImplementsLocked.new, "ImplementsLocked"]
|
||||
]) {
|
||||
Expect.throws(() {
|
||||
Isolate.exit(sendPort, (pairFunctionName[0] as Function)());
|
||||
}, (e) {
|
||||
return e is ArgumentError &&
|
||||
e
|
||||
.toString()
|
||||
.contains(RegExp("unsendable object .+${pairFunctionName[1]}"));
|
||||
});
|
||||
}
|
||||
}, rp.sendPort, onError: rpError.sendPort, onExit: rpExit.sendPort);
|
||||
await rpExit.first;
|
||||
rpError.close();
|
||||
rp.close();
|
||||
asyncEnd();
|
||||
}
|
|
@ -39,26 +39,65 @@ class Fu {
|
|||
}
|
||||
}
|
||||
|
||||
void checkForRetainingPath(Object? e, List<String> list) {
|
||||
Expect.isTrue(e is ArgumentError);
|
||||
@pragma("vm:isolate-unsendable")
|
||||
class Locked {}
|
||||
|
||||
class ExtendsLocked extends Locked {}
|
||||
|
||||
class ImplementsLocked implements Locked {}
|
||||
|
||||
bool checkForRetainingPath(Object? e, List<String> list) {
|
||||
if (e is! ArgumentError) {
|
||||
return false;
|
||||
}
|
||||
final msg = e.toString();
|
||||
list.forEach((s) {
|
||||
Expect.contains(s, msg);
|
||||
});
|
||||
return list.every((s) => msg.contains(s));
|
||||
}
|
||||
|
||||
main() async {
|
||||
asyncStart();
|
||||
final rp = ReceivePort();
|
||||
try {
|
||||
rp.sendPort.send(Fu.unsendable('fu'));
|
||||
} catch (e) {
|
||||
checkForRetainingPath(e, <String>[
|
||||
'NativeWrapper',
|
||||
'Baz',
|
||||
'Fu',
|
||||
]);
|
||||
|
||||
for (final pair in [
|
||||
[
|
||||
() => Fu.unsendable('fu'),
|
||||
["NativeClass", "Baz", "Fu"]
|
||||
],
|
||||
[
|
||||
() => Future.value(123),
|
||||
["Future"]
|
||||
],
|
||||
[
|
||||
Locked.new,
|
||||
["Locked"]
|
||||
],
|
||||
[
|
||||
ExtendsLocked.new,
|
||||
["ExtendsLocked"]
|
||||
],
|
||||
[
|
||||
ImplementsLocked.new,
|
||||
["ImplementsLocked"]
|
||||
]
|
||||
]) {
|
||||
Expect.throws(() => rp.sendPort.send((pair[0] as Function)()),
|
||||
(e) => checkForRetainingPath(e, pair[1] as List<String>));
|
||||
}
|
||||
|
||||
try {
|
||||
await Isolate.spawn((_) {}, Locked());
|
||||
Expect.fail('spawn should have failed');
|
||||
} catch (e) {
|
||||
Expect.isTrue(e is ArgumentError && e.toString().contains("Locked"));
|
||||
}
|
||||
|
||||
runZoned(() {
|
||||
Expect.throws(() {
|
||||
final z = Zone.current;
|
||||
rp.sendPort.send(Zone.current);
|
||||
}, (e) => checkForRetainingPath(e, <String>['Zone']));
|
||||
}, zoneValues: {0: 1});
|
||||
|
||||
rp.close();
|
||||
asyncEnd();
|
||||
}
|
||||
|
|
46
runtime/tests/vm/dart_2/isolate_exit_unsendable_test.dart
Normal file
46
runtime/tests/vm/dart_2/isolate_exit_unsendable_test.dart
Normal file
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) 2023, 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.
|
||||
|
||||
// @dart = 2.9
|
||||
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:async_helper/async_helper.dart';
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
@pragma('vm:isolate-unsendable')
|
||||
class Locked {}
|
||||
|
||||
class ExtendsLocked extends Locked {}
|
||||
|
||||
class ImplementsLocked implements Locked {}
|
||||
|
||||
main() async {
|
||||
asyncStart();
|
||||
final rpExit = ReceivePort();
|
||||
final rpError = RawReceivePort((e) {
|
||||
Expect.fail('Spawned isolated failed with $e');
|
||||
});
|
||||
|
||||
final rp = RawReceivePort((e) {
|
||||
Expect.fail('Received unexpected $e, no objects should have arrived');
|
||||
});
|
||||
await Isolate.spawn((sendPort) {
|
||||
for (final fun in [
|
||||
() => Locked(),
|
||||
() => ExtendsLocked(),
|
||||
() => ImplementsLocked()
|
||||
]) {
|
||||
Expect.throws(() {
|
||||
Isolate.exit(sendPort, fun());
|
||||
}, (e) {
|
||||
return e is ArgumentError && e.toString().contains("unsendable object");
|
||||
});
|
||||
}
|
||||
}, rp.sendPort, onError: rpError.sendPort, onExit: rpExit.sendPort);
|
||||
await rpExit.first;
|
||||
rpError.close();
|
||||
rp.close();
|
||||
asyncEnd();
|
||||
}
|
|
@ -41,26 +41,65 @@ class Fu {
|
|||
}
|
||||
}
|
||||
|
||||
void checkForRetainingPath(Object e, List<String> list) {
|
||||
Expect.isTrue(e is ArgumentError);
|
||||
@pragma("vm:isolate-unsendable")
|
||||
class Locked {}
|
||||
|
||||
class ExtendsLocked extends Locked {}
|
||||
|
||||
class ImplementsLocked implements Locked {}
|
||||
|
||||
bool checkForRetainingPath(Object e, List<String> list) {
|
||||
if (e is! ArgumentError) {
|
||||
return false;
|
||||
}
|
||||
final msg = e.toString();
|
||||
list.forEach((s) {
|
||||
Expect.contains(s, msg);
|
||||
});
|
||||
return list.every((s) => msg.contains(s));
|
||||
}
|
||||
|
||||
main() async {
|
||||
asyncStart();
|
||||
final rp = ReceivePort();
|
||||
try {
|
||||
rp.sendPort.send(Fu.unsendable('fu'));
|
||||
} catch (e) {
|
||||
checkForRetainingPath(e, <String>[
|
||||
'NativeWrapper',
|
||||
'Baz',
|
||||
'Fu',
|
||||
]);
|
||||
|
||||
for (final pair in [
|
||||
[
|
||||
() => Fu.unsendable('fu'),
|
||||
["NativeClass", "Baz", "Fu"]
|
||||
],
|
||||
[
|
||||
() => Future.value(123),
|
||||
["Future"]
|
||||
],
|
||||
[
|
||||
() => Locked(),
|
||||
["Locked"]
|
||||
],
|
||||
[
|
||||
() => ExtendsLocked(),
|
||||
["ExtendsLocked"]
|
||||
],
|
||||
[
|
||||
() => ImplementsLocked(),
|
||||
["ImplementsLocked"]
|
||||
]
|
||||
]) {
|
||||
Expect.throws(() => rp.sendPort.send((pair[0] as Function)()),
|
||||
(e) => checkForRetainingPath(e, pair[1] as List<String>));
|
||||
}
|
||||
|
||||
try {
|
||||
await Isolate.spawn((_) {}, Locked());
|
||||
Expect.fail('spawn should have failed');
|
||||
} catch (e) {
|
||||
Expect.isTrue(e is ArgumentError && e.toString().contains("Locked"));
|
||||
}
|
||||
|
||||
runZoned(() {
|
||||
Expect.throws(() {
|
||||
final z = Zone.current;
|
||||
rp.sendPort.send(Zone.current);
|
||||
}, (e) => checkForRetainingPath(e, <String>['Zone']));
|
||||
}, zoneValues: {0: 1});
|
||||
|
||||
rp.close();
|
||||
asyncEnd();
|
||||
}
|
||||
|
|
|
@ -1088,6 +1088,7 @@ void ClassFinalizer::FinalizeTypesInClass(const Class& cls) {
|
|||
bool implements_finalizable =
|
||||
cls.Name() == Symbols::Finalizable().ptr() &&
|
||||
Library::UrlOf(cls.library()) == Symbols::DartFfi().ptr();
|
||||
bool is_isolate_unsendable = cls.is_isolate_unsendable();
|
||||
|
||||
// Finalize super class.
|
||||
Class& super_class = Class::Handle(zone, cls.SuperClass());
|
||||
|
@ -1106,6 +1107,8 @@ void ClassFinalizer::FinalizeTypesInClass(const Class& cls) {
|
|||
cls.set_super_type(super_type);
|
||||
implements_finalizable |=
|
||||
Class::ImplementsFinalizable(super_type.type_class());
|
||||
is_isolate_unsendable |=
|
||||
Class::IsIsolateUnsendable(super_type.type_class());
|
||||
}
|
||||
// Finalize interface types (but not necessarily interface classes).
|
||||
const auto& interface_types = Array::Handle(zone, cls.interfaces());
|
||||
|
@ -1120,9 +1123,12 @@ void ClassFinalizer::FinalizeTypesInClass(const Class& cls) {
|
|||
interface_types.SetAt(i, interface_type);
|
||||
implements_finalizable |=
|
||||
Class::ImplementsFinalizable(interface_type.type_class());
|
||||
is_isolate_unsendable |=
|
||||
Class::IsIsolateUnsendable(interface_type.type_class());
|
||||
}
|
||||
cls.set_implements_finalizable(implements_finalizable);
|
||||
cls.set_is_type_finalized();
|
||||
cls.set_is_isolate_unsendable(is_isolate_unsendable);
|
||||
|
||||
RegisterClassInHierarchy(thread->zone(), cls);
|
||||
#endif // defined(DART_PRECOMPILED_RUNTIME)
|
||||
|
|
|
@ -192,7 +192,7 @@ KernelLoader::KernelLoader(Program* program,
|
|||
patch_classes_(Array::ZoneHandle(zone_)),
|
||||
active_class_(),
|
||||
library_kernel_offset_(-1), // Set to the correct value in LoadLibrary
|
||||
correction_offset_(-1), // Set to the correct value in LoadLibrary
|
||||
correction_offset_(-1), // Set to the correct value in LoadLibrary
|
||||
loading_native_wrappers_library_(false),
|
||||
library_kernel_data_(ExternalTypedData::ZoneHandle(zone_)),
|
||||
kernel_program_info_(KernelProgramInfo::ZoneHandle(zone_)),
|
||||
|
@ -1045,6 +1045,7 @@ void KernelLoader::FinishTopLevelClassLoading(
|
|||
bool has_pragma_annotation;
|
||||
ReadVMAnnotations(library, annotation_count, /*native_name=*/nullptr,
|
||||
/*is_invisible_function=*/nullptr,
|
||||
/*is_isolate_unsendable=*/nullptr,
|
||||
&has_pragma_annotation);
|
||||
field_helper.SetJustRead(FieldHelper::kAnnotations);
|
||||
|
||||
|
@ -1356,8 +1357,13 @@ void KernelLoader::LoadClass(const Library& library,
|
|||
class_helper.ReadUntilExcluding(ClassHelper::kAnnotations);
|
||||
intptr_t annotation_count = helper_.ReadListLength();
|
||||
bool has_pragma_annotation = false;
|
||||
bool is_isolate_unsendable = false;
|
||||
ReadVMAnnotations(library, annotation_count, /*native_name=*/nullptr,
|
||||
/*is_invisible_function=*/nullptr, &has_pragma_annotation);
|
||||
/*is_invisible_function=*/nullptr, &is_isolate_unsendable,
|
||||
&has_pragma_annotation);
|
||||
if (is_isolate_unsendable) {
|
||||
out_class->set_is_isolate_unsendable(true);
|
||||
}
|
||||
if (has_pragma_annotation) {
|
||||
out_class->set_has_pragma(true);
|
||||
}
|
||||
|
@ -1437,6 +1443,7 @@ void KernelLoader::FinishClassLoading(const Class& klass,
|
|||
bool has_pragma_annotation;
|
||||
ReadVMAnnotations(library, annotation_count, /*native_name=*/nullptr,
|
||||
/*is_invisible_function=*/nullptr,
|
||||
/*is_isolate_unsendable=*/nullptr,
|
||||
&has_pragma_annotation);
|
||||
field_helper.SetJustRead(FieldHelper::kAnnotations);
|
||||
|
||||
|
@ -1563,7 +1570,8 @@ void KernelLoader::FinishClassLoading(const Class& klass,
|
|||
bool has_pragma_annotation;
|
||||
bool is_invisible_function;
|
||||
ReadVMAnnotations(library, annotation_count, /*native_name=*/nullptr,
|
||||
&is_invisible_function, &has_pragma_annotation);
|
||||
&is_invisible_function, /*isolate_unsendable=*/nullptr,
|
||||
&has_pragma_annotation);
|
||||
constructor_helper.SetJustRead(ConstructorHelper::kAnnotations);
|
||||
constructor_helper.ReadUntilExcluding(ConstructorHelper::kFunction);
|
||||
|
||||
|
@ -1705,7 +1713,8 @@ void KernelLoader::FinishLoading(const Class& klass) {
|
|||
class_index, &class_helper);
|
||||
}
|
||||
|
||||
// Read annotations on a procedure to identify potential VM-specific directives.
|
||||
// Read annotations on a procedure or a class to identify potential VM-specific
|
||||
// directives.
|
||||
//
|
||||
// Output parameters:
|
||||
//
|
||||
|
@ -1720,6 +1729,7 @@ void KernelLoader::ReadVMAnnotations(const Library& library,
|
|||
intptr_t annotation_count,
|
||||
String* native_name,
|
||||
bool* is_invisible_function,
|
||||
bool* is_isolate_unsendable,
|
||||
bool* has_pragma_annotation) {
|
||||
if (is_invisible_function != nullptr) {
|
||||
*is_invisible_function = false;
|
||||
|
@ -1757,6 +1767,12 @@ void KernelLoader::ReadVMAnnotations(const Library& library,
|
|||
constant_reader.GetStringConstant(options_index, native_name);
|
||||
}
|
||||
}
|
||||
if (is_isolate_unsendable != nullptr) {
|
||||
if (constant_reader.IsStringConstant(name_index,
|
||||
"vm:isolate-unsendable")) {
|
||||
*is_isolate_unsendable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
helper_.SkipExpression();
|
||||
|
@ -1796,7 +1812,8 @@ void KernelLoader::LoadProcedure(const Library& library,
|
|||
bool is_invisible_function;
|
||||
const intptr_t annotation_count = helper_.ReadListLength();
|
||||
ReadVMAnnotations(library, annotation_count, &native_name,
|
||||
&is_invisible_function, &has_pragma_annotation);
|
||||
&is_invisible_function, /*isolate_unsendable=*/nullptr,
|
||||
&has_pragma_annotation);
|
||||
is_external = is_external && native_name.IsNull();
|
||||
procedure_helper.SetJustRead(ProcedureHelper::kAnnotations);
|
||||
const Object& script_class =
|
||||
|
|
|
@ -228,6 +228,7 @@ class KernelLoader : public ValueObject {
|
|||
intptr_t annotation_count,
|
||||
String* native_name,
|
||||
bool* is_invisible_function,
|
||||
bool* is_isolate_unsendable,
|
||||
bool* has_pragma_annotation);
|
||||
|
||||
KernelLoader(const Script& script,
|
||||
|
|
|
@ -3149,6 +3149,11 @@ void Class::set_has_pragma(bool value) const {
|
|||
set_state_bits(HasPragmaBit::update(value, state_bits()));
|
||||
}
|
||||
|
||||
void Class::set_is_isolate_unsendable(bool value) const {
|
||||
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
|
||||
set_state_bits(IsIsolateUnsendableBit::update(value, state_bits()));
|
||||
}
|
||||
|
||||
void Class::set_implements_finalizable(bool value) const {
|
||||
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
|
||||
set_state_bits(ImplementsFinalizableBit::update(value, state_bits()));
|
||||
|
@ -4992,6 +4997,7 @@ ClassPtr Class::NewNativeWrapper(const Library& library,
|
|||
cls.set_is_declaration_loaded();
|
||||
cls.set_is_type_finalized();
|
||||
cls.set_is_synthesized_class();
|
||||
cls.set_is_isolate_unsendable(true);
|
||||
NOT_IN_PRECOMPILED(cls.set_implementor_cid(kDynamicCid));
|
||||
library.AddClass(cls);
|
||||
return cls.ptr();
|
||||
|
|
|
@ -1683,6 +1683,9 @@ class Class : public Object {
|
|||
ASSERT(Class::Handle(clazz).is_type_finalized());
|
||||
return ImplementsFinalizableBit::decode(clazz->untag()->state_bits_);
|
||||
}
|
||||
static bool IsIsolateUnsendable(ClassPtr clazz) {
|
||||
return IsIsolateUnsendableBit::decode(clazz->untag()->state_bits_);
|
||||
}
|
||||
|
||||
#if !defined(DART_PRECOMPILED_RUNTIME)
|
||||
CodePtr allocation_stub() const { return untag()->allocation_stub(); }
|
||||
|
@ -1924,6 +1927,12 @@ class Class : public Object {
|
|||
kBaseClassBit,
|
||||
kInterfaceClassBit,
|
||||
kFinalBit,
|
||||
// Whether instances of the class cannot be sent across ports.
|
||||
//
|
||||
// Will be true iff
|
||||
// - class is marked with `@pramga('vm:isolate-unsendable')
|
||||
// - super class / super interface classes are marked as unsendable.
|
||||
kIsIsolateUnsendableBit,
|
||||
};
|
||||
class ConstBit : public BitField<uint32_t, bool, kConstBit, 1> {};
|
||||
class ImplementedBit : public BitField<uint32_t, bool, kImplementedBit, 1> {};
|
||||
|
@ -1954,6 +1963,8 @@ class Class : public Object {
|
|||
class InterfaceClassBit
|
||||
: public BitField<uint32_t, bool, kInterfaceClassBit, 1> {};
|
||||
class FinalBit : public BitField<uint32_t, bool, kFinalBit, 1> {};
|
||||
class IsIsolateUnsendableBit
|
||||
: public BitField<uint32_t, bool, kIsIsolateUnsendableBit, 1> {};
|
||||
|
||||
void set_name(const String& value) const;
|
||||
void set_user_name(const String& value) const;
|
||||
|
@ -1993,7 +2004,12 @@ class Class : public Object {
|
|||
void set_num_type_arguments_unsafe(intptr_t value) const;
|
||||
|
||||
bool has_pragma() const { return HasPragmaBit::decode(state_bits()); }
|
||||
void set_has_pragma(bool has_pragma) const;
|
||||
void set_has_pragma(bool value) const;
|
||||
|
||||
void set_is_isolate_unsendable(bool value) const;
|
||||
bool is_isolate_unsendable() const {
|
||||
return IsIsolateUnsendableBit::decode(state_bits());
|
||||
}
|
||||
|
||||
bool implements_finalizable() const {
|
||||
ASSERT(is_type_finalized());
|
||||
|
|
|
@ -826,29 +826,17 @@ class ObjectCopyBase {
|
|||
DART_FORCE_INLINE
|
||||
bool CanCopyObject(uword tags, ObjectPtr object) {
|
||||
const auto cid = UntaggedObject::ClassIdTag::decode(tags);
|
||||
if (Class::IsIsolateUnsendable(class_table_->At(cid))) {
|
||||
exception_msg_ = OS::SCreate(
|
||||
zone_,
|
||||
"Illegal argument in isolate message: object is unsendable - %s ("
|
||||
"see restrictions listed at `SendPort.send()` documentation "
|
||||
"for more information)",
|
||||
Class::Handle(class_table_->At(cid)).ToCString());
|
||||
exception_unexpected_object_ = object;
|
||||
return false;
|
||||
}
|
||||
if (cid > kNumPredefinedCids) {
|
||||
const bool has_native_fields =
|
||||
Class::NumNativeFieldsOf(class_table_->At(cid)) != 0;
|
||||
if (has_native_fields) {
|
||||
exception_msg_ =
|
||||
OS::SCreate(zone_,
|
||||
"Illegal argument in isolate message: (object extends "
|
||||
"NativeWrapper - %s)",
|
||||
Class::Handle(class_table_->At(cid)).ToCString());
|
||||
exception_unexpected_object_ = object;
|
||||
return false;
|
||||
}
|
||||
const bool implements_finalizable =
|
||||
Class::ImplementsFinalizable(class_table_->At(cid));
|
||||
if (implements_finalizable) {
|
||||
exception_msg_ = OS::SCreate(
|
||||
zone_,
|
||||
"Illegal argument in isolate message: (object implements "
|
||||
"Finalizable - %s)",
|
||||
Class::Handle(class_table_->At(cid)).ToCString());
|
||||
exception_unexpected_object_ = object;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#define HANDLE_ILLEGAL_CASE(Type) \
|
||||
|
@ -1030,12 +1018,9 @@ class RetainingPath {
|
|||
}
|
||||
// These we are not expected to drill into as they can't be on
|
||||
// retaining path, they are illegal to send.
|
||||
if (cid >= kNumPredefinedCids) {
|
||||
klass = class_table->At(cid);
|
||||
if ((klass.num_native_fields() != 0) ||
|
||||
(klass.implements_finalizable())) {
|
||||
break;
|
||||
}
|
||||
klass = class_table->At(cid);
|
||||
if (klass.is_isolate_unsendable()) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ASSERT(traversal_rules_ ==
|
||||
|
|
|
@ -517,6 +517,7 @@ class ObjectPointerVisitor;
|
|||
V(vm_ffi_native_assets, "vm:ffi:native-assets") \
|
||||
V(vm_ffi_struct_fields, "vm:ffi:struct-fields") \
|
||||
V(vm_invisible, "vm:invisible") \
|
||||
V(vm_isolate_unsendable, "vm:isolate-unsendable") \
|
||||
V(vm_never_inline, "vm:never-inline") \
|
||||
V(vm_non_nullable_result_type, "vm:non-nullable-result-type") \
|
||||
V(vm_notify_debugger_on_exception, "vm:notify-debugger-on-exception") \
|
||||
|
|
|
@ -10,6 +10,7 @@ import 'dart:typed_data';
|
|||
|
||||
@patch
|
||||
@pragma("vm:entry-point")
|
||||
@vmIsolateUnsendable
|
||||
abstract interface class Finalizable {}
|
||||
|
||||
@patch
|
||||
|
|
|
@ -116,7 +116,8 @@ import "dart:_internal"
|
|||
printToConsole,
|
||||
Since,
|
||||
typeAcceptsNull,
|
||||
unsafeCast;
|
||||
unsafeCast,
|
||||
vmIsolateUnsendable;
|
||||
|
||||
part 'async_error.dart';
|
||||
part 'broadcast_stream_controller.dart';
|
||||
|
|
|
@ -224,6 +224,7 @@ abstract class FutureOr<T> {
|
|||
/// called. That situation should generally be avoided if possible, unless
|
||||
/// it's very clearly documented.
|
||||
@pragma("wasm:entry-point")
|
||||
@vmIsolateUnsendable
|
||||
abstract interface class Future<T> {
|
||||
/// A `Future<Null>` completed with `null`.
|
||||
///
|
||||
|
@ -1163,6 +1164,7 @@ class TimeoutException implements Exception {
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
@vmIsolateUnsendable
|
||||
abstract interface class Completer<T> {
|
||||
/// Creates a new completer.
|
||||
///
|
||||
|
|
|
@ -133,6 +133,7 @@ typedef void _TimerCallback();
|
|||
/// A broadcast stream inheriting from [Stream] must override [isBroadcast]
|
||||
/// to return `true` if it wants to signal that it behaves like a broadcast
|
||||
/// stream.
|
||||
@vmIsolateUnsendable
|
||||
abstract mixin class Stream<T> {
|
||||
const Stream();
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ part of dart.async;
|
|||
///
|
||||
/// See also:
|
||||
/// * [Stopwatch] for measuring elapsed time.
|
||||
@vmIsolateUnsendable
|
||||
abstract interface class Timer {
|
||||
/// Creates a new timer.
|
||||
///
|
||||
|
|
|
@ -570,6 +570,7 @@ abstract final class ZoneDelegate {
|
|||
/// Similarly, zones provide [bindCallbackGuarded] (and the corresponding
|
||||
/// [bindUnaryCallbackGuarded] and [bindBinaryCallbackGuarded]), when the
|
||||
/// callback should be invoked through [Zone.runGuarded].
|
||||
@vmIsolateUnsendable
|
||||
abstract final class Zone {
|
||||
// Private constructor so that it is not possible instantiate a Zone class.
|
||||
Zone._();
|
||||
|
|
|
@ -1018,3 +1018,19 @@ class DoubleLinkedQueueEntry<E> {
|
|||
/// The next entry, or `null` if there is none.
|
||||
DoubleLinkedQueueEntry<E>? nextEntry() => _nextLink;
|
||||
}
|
||||
|
||||
/// Annotation on a class preventing instances from being sent between isolates.
|
||||
///
|
||||
/// Applies to class, mixin or enum declarations, and is inherited by subclasses
|
||||
/// along extends, with and implements relations.
|
||||
///
|
||||
/// An instance of a class with this annotation will be prevented from being
|
||||
/// part of isolate communication. It cannot be sent through a SendPort,
|
||||
/// not even if both ends are in the same isolate, and it cannot be part of
|
||||
/// the initial message of isolate spawn operations.
|
||||
///
|
||||
/// The annotation is intended for classes which have a dynamic link
|
||||
/// to the current isolate, for example being tied to the event loop
|
||||
/// through scheduled events or timers, which would put the object into
|
||||
/// an inconsistent state if simply being copied.
|
||||
const vmIsolateUnsendable = pragma("vm:isolate-unsendable");
|
||||
|
|
|
@ -800,6 +800,10 @@ abstract class SendPort implements Capability {
|
|||
/// - [UserTag]
|
||||
/// - `MirrorReference`
|
||||
///
|
||||
/// Instances of classes that either themselves are marked with
|
||||
/// `@pragma('vm:isolate-unsendable')`, extend or implement such classes
|
||||
/// cannot be sent through the ports.
|
||||
///
|
||||
/// Apart from those exceptions any object can be sent. Objects that are
|
||||
/// identified as immutable (e.g. strings) will be shared whereas all other
|
||||
/// objects will be copied.
|
||||
|
|
|
@ -62,7 +62,7 @@ Future<void> testSendAndExitFinalizable() async {
|
|||
receivePort.sendPort,
|
||||
);
|
||||
final result = await receivePort.first;
|
||||
Expect.contains("Invalid argument: is a Finalizable", result);
|
||||
Expect.contains("Invalid argument: is unsendable", result);
|
||||
}
|
||||
|
||||
Future<void> testSendAndExitFinalizer() async {
|
||||
|
@ -79,5 +79,5 @@ Future<void> testSendAndExitFinalizer() async {
|
|||
receivePort.sendPort,
|
||||
);
|
||||
final result = await receivePort.first;
|
||||
Expect.contains("Invalid argument: is a Finalizable", result);
|
||||
Expect.contains("Invalid argument: is unsendable", result);
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ Future<void> testSendAndExitFinalizable() async {
|
|||
receivePort.sendPort,
|
||||
);
|
||||
final result = await receivePort.first;
|
||||
Expect.contains("Invalid argument: is a Finalizable", result);
|
||||
Expect.contains("Invalid argument: is unsendable", result);
|
||||
}
|
||||
|
||||
Future<void> testSendAndExitFinalizer() async {
|
||||
|
@ -81,5 +81,5 @@ Future<void> testSendAndExitFinalizer() async {
|
|||
receivePort.sendPort,
|
||||
);
|
||||
final result = await receivePort.first;
|
||||
Expect.contains("Invalid argument: is a Finalizable", result);
|
||||
Expect.contains("Invalid argument: is unsendable", result);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ main() {
|
|||
asyncStart();
|
||||
Future<Isolate?>.value(snd).catchError((e) {
|
||||
Expect.isTrue(e is ArgumentError);
|
||||
Expect.isTrue("$e".contains("NativeWrapper"));
|
||||
Expect.isTrue("$e".contains("Test"));
|
||||
port.close();
|
||||
asyncEnd();
|
||||
});
|
||||
|
|
|
@ -29,7 +29,7 @@ main() {
|
|||
asyncStart();
|
||||
snd.catchError((e) {
|
||||
Expect.isTrue(e is ArgumentError);
|
||||
Expect.isTrue("$e".contains("NativeWrapper"));
|
||||
Expect.isTrue("$e".contains("Test"));
|
||||
port.close();
|
||||
asyncEnd();
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue