[vm/shared] Prohibit capturing of 'late final' variables by isolategroup-bound closures.
TEST=run_isolate_group_run_test BUG=https://github.com/dart-lang/sdk/issues/62181 Change-Id: I50037ede337367020176262b98d2c2fd100b050a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/466820 Reviewed-by: Slava Egorov <vegorov@google.com> Commit-Queue: Alexander Aprelev <aam@google.com>
This commit is contained in:
committed by
Commit Queue
parent
420ac4d329
commit
170df25b66
@@ -681,7 +681,7 @@ type ClosureDeclaration {
|
||||
|
||||
type ClosureCode {
|
||||
UInt flags = (hasExceptionsTable, hasSourcePositions, hasLocalVariables,
|
||||
capturesOnlyFinalAndSharedVars)
|
||||
capturesOnlyFinalNotLateVars)
|
||||
|
||||
UInt bytecodeSizeInBytes;
|
||||
Byte[bytecodeSizeInBytes] bytecodes;
|
||||
|
||||
@@ -1671,7 +1671,7 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
currentLoopDepth = 0;
|
||||
savedMaxSourcePositions = <int>[];
|
||||
|
||||
locals = new LocalVariables(pragmaParser, node, options, staticTypeContext);
|
||||
locals = new LocalVariables(node, options, staticTypeContext);
|
||||
locals.enterScope(node);
|
||||
|
||||
final int startPosition;
|
||||
@@ -2508,14 +2508,14 @@ class BytecodeGenerator extends RecursiveVisitor {
|
||||
currentLoopDepth = savedLoopDepth;
|
||||
asyncTryBlock = savedAsyncTryBlock;
|
||||
|
||||
bool capturesOnlyFinalAndSharedVars =
|
||||
locals.capturesOnlyFinalAndSharedVars;
|
||||
bool capturesOnlyFinalNotLateVars =
|
||||
locals.capturesOnlyFinalNotLateVars;
|
||||
|
||||
locals.leaveScope();
|
||||
|
||||
closure.code = new ClosureCode(asm.bytecode, asm.exceptionsTable,
|
||||
finalizeSourcePositions(), finalizeLocalVariables(),
|
||||
capturesOnlyFinalAndSharedVars);
|
||||
capturesOnlyFinalNotLateVars);
|
||||
|
||||
_popAssemblerState();
|
||||
|
||||
|
||||
@@ -1162,13 +1162,13 @@ class ClosureCode {
|
||||
static const hasExceptionsTableFlag = 1 << 0;
|
||||
static const hasSourcePositionsFlag = 1 << 1;
|
||||
static const hasLocalVariablesFlag = 1 << 2;
|
||||
static const capturesOnlyFinalAndSharedVarsFlag = 1 << 3;
|
||||
static const capturesOnlyFinalNotLateVarsFlag = 1 << 3;
|
||||
|
||||
final Uint8List bytecodes;
|
||||
final ExceptionsTable exceptionsTable;
|
||||
final SourcePositions? sourcePositions;
|
||||
final LocalVariableTable? localVariables;
|
||||
final bool capturesOnlyFinalAndSharedVars;
|
||||
final bool capturesOnlyFinalNotLateVars;
|
||||
|
||||
bool get hasExceptionsTable => exceptionsTable.blocks.isNotEmpty;
|
||||
bool get hasSourcePositions => sourcePositions?.isNotEmpty ?? false;
|
||||
@@ -1178,10 +1178,10 @@ class ClosureCode {
|
||||
(hasExceptionsTable ? hasExceptionsTableFlag : 0) |
|
||||
(hasSourcePositions ? hasSourcePositionsFlag : 0) |
|
||||
(hasLocalVariables ? hasLocalVariablesFlag : 0) |
|
||||
(capturesOnlyFinalAndSharedVars ? capturesOnlyFinalAndSharedVarsFlag : 0);
|
||||
(capturesOnlyFinalNotLateVars ? capturesOnlyFinalNotLateVarsFlag : 0);
|
||||
|
||||
ClosureCode(this.bytecodes, this.exceptionsTable, this.sourcePositions,
|
||||
this.localVariables, this.capturesOnlyFinalAndSharedVars);
|
||||
this.localVariables, this.capturesOnlyFinalNotLateVars);
|
||||
|
||||
void write(BufferedWriter writer) {
|
||||
writer.writePackedUInt30(flags);
|
||||
@@ -1209,12 +1209,12 @@ class ClosureCode {
|
||||
final localVariables = ((flags & hasLocalVariablesFlag) != 0)
|
||||
? reader.readLinkOffset<LocalVariableTable>()
|
||||
: null;
|
||||
final capturesOnlyFinalAndSharedVars =
|
||||
(flags & capturesOnlyFinalAndSharedVarsFlag) != 0;
|
||||
final capturesOnlyFinalNotLateVars =
|
||||
(flags & capturesOnlyFinalNotLateVarsFlag) != 0;
|
||||
|
||||
return new ClosureCode(
|
||||
bytecodes, exceptionsTable, sourcePositions, localVariables,
|
||||
capturesOnlyFinalAndSharedVars);
|
||||
capturesOnlyFinalNotLateVars);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -7,8 +7,6 @@ import 'dart:math' show min, max;
|
||||
import 'package:kernel/ast.dart';
|
||||
import 'package:kernel/type_environment.dart';
|
||||
|
||||
import 'package:vm/transformations/pragma.dart';
|
||||
|
||||
import 'dbc.dart';
|
||||
import 'options.dart' show BytecodeOptions;
|
||||
|
||||
@@ -159,8 +157,8 @@ class LocalVariables {
|
||||
List<VariableDeclaration> get sortedNamedParameters =>
|
||||
_currentFrame.sortedNamedParameters;
|
||||
|
||||
LocalVariables(PragmaAnnotationParser pragmaParser, Member node, this.options, this.staticTypeContext) {
|
||||
final scopeBuilder = new _ScopeBuilder(pragmaParser, this);
|
||||
LocalVariables(Member node, this.options, this.staticTypeContext) {
|
||||
final scopeBuilder = new _ScopeBuilder(this);
|
||||
node.accept(scopeBuilder);
|
||||
|
||||
final allocator = new _Allocator(this);
|
||||
@@ -177,8 +175,8 @@ class LocalVariables {
|
||||
_currentFrameInternal = _currentScopeInternal?.frame;
|
||||
}
|
||||
|
||||
bool get capturesOnlyFinalAndSharedVars =>
|
||||
_currentFrame.capturesOnlyFinalAndSharedVars;
|
||||
bool get capturesOnlyFinalNotLateVars =>
|
||||
_currentFrame.capturesOnlyFinalNotLateVars;
|
||||
|
||||
void withTemp(TreeNode node, int temp, void action()) {
|
||||
final old = _temps![node];
|
||||
@@ -197,18 +195,17 @@ class VarDesc {
|
||||
final VariableDeclaration declaration;
|
||||
Scope scope;
|
||||
bool isCaptured = false;
|
||||
bool _isShared;
|
||||
int? index;
|
||||
int? originalParamSlotIndex;
|
||||
|
||||
VarDesc(this.declaration, this.scope, this._isShared) {
|
||||
VarDesc(this.declaration, this.scope) {
|
||||
scope.vars.add(this);
|
||||
}
|
||||
|
||||
Frame get frame => scope.frame;
|
||||
|
||||
bool get isAllocated => index != null;
|
||||
bool get isFinalOrShared => declaration.isFinal || _isShared;
|
||||
bool get isFinalNotLate => declaration.isFinal && !declaration.isLate;
|
||||
|
||||
void capture() {
|
||||
assert(!isAllocated);
|
||||
@@ -250,7 +247,7 @@ class Frame {
|
||||
int frameSize = 0;
|
||||
List<int> temporaries = <int>[];
|
||||
int? contextLevelAtEntry;
|
||||
bool capturesOnlyFinalAndSharedVars = true;
|
||||
bool capturesOnlyFinalNotLateVars = true;
|
||||
|
||||
Frame(this.function, this.parent);
|
||||
|
||||
@@ -310,9 +307,7 @@ class _ScopeBuilder extends RecursiveVisitor {
|
||||
List<TreeNode> _enclosingTryCatches = const [];
|
||||
int _loopDepth = 0;
|
||||
|
||||
final PragmaAnnotationParser _pragmaParser;
|
||||
|
||||
_ScopeBuilder(this._pragmaParser, this.locals);
|
||||
_ScopeBuilder(this.locals);
|
||||
|
||||
List<VariableDeclaration> _sortNamedParameters(FunctionNode function) {
|
||||
final params = function.namedParameters.toList();
|
||||
@@ -472,10 +467,7 @@ class _ScopeBuilder extends RecursiveVisitor {
|
||||
if (scope == null) {
|
||||
scope = _currentScope;
|
||||
}
|
||||
final isShared = _pragmaParser
|
||||
.parsedPragmas<ParsedVmSharedPragma>(variable.annotations)
|
||||
.isNotEmpty;
|
||||
final VarDesc v = new VarDesc(variable, scope, isShared);
|
||||
final VarDesc v = new VarDesc(variable, scope);
|
||||
assert(locals._vars[variable] == null,
|
||||
'Double declaring variable ${variable}!');
|
||||
locals._vars[variable] = v;
|
||||
@@ -488,8 +480,8 @@ class _ScopeBuilder extends RecursiveVisitor {
|
||||
}
|
||||
if (v.frame != _currentFrame) {
|
||||
v.capture();
|
||||
if (!v.isFinalOrShared) {
|
||||
_currentFrame.capturesOnlyFinalAndSharedVars = false;
|
||||
if (!v.isFinalNotLate) {
|
||||
_currentFrame.capturesOnlyFinalNotLateVars = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,14 +304,14 @@ void BytecodeReaderHelper::ReadCode(const Function& function,
|
||||
(flags & ClosureCode::kHasSourcePositionsFlag) != 0;
|
||||
const bool has_local_variables =
|
||||
(flags & ClosureCode::kHasLocalVariablesFlag) != 0;
|
||||
const bool does_close_over_only_final_and_shared_vars =
|
||||
(flags & ClosureCode::kCapturesOnlyFinalAndSharedVarsFlag) != 0;
|
||||
const bool captures_only_final_not_late_vars =
|
||||
(flags & ClosureCode::kCapturesOnlyFinalNotLateVarsFlag) != 0;
|
||||
|
||||
// Read closure bytecode and attach to closure function.
|
||||
closure_bytecode = ReadBytecode(pool);
|
||||
|
||||
closure.set_does_close_over_only_final_and_shared_vars(
|
||||
does_close_over_only_final_and_shared_vars);
|
||||
closure.set_captures_only_final_not_late_vars(
|
||||
captures_only_final_not_late_vars);
|
||||
closure.AttachBytecode(closure_bytecode);
|
||||
|
||||
ReadExceptionsTable(closure, closure_bytecode, has_exceptions_table);
|
||||
|
||||
@@ -308,7 +308,7 @@ class BytecodeReaderHelper : public ValueObject {
|
||||
static const int kHasExceptionsTableFlag = 1 << 0;
|
||||
static const int kHasSourcePositionsFlag = 1 << 1;
|
||||
static const int kHasLocalVariablesFlag = 1 << 2;
|
||||
static const int kCapturesOnlyFinalAndSharedVarsFlag = 1 << 3;
|
||||
static const int kCapturesOnlyFinalNotLateVarsFlag = 1 << 3;
|
||||
};
|
||||
|
||||
// Parameter flags, must be in sync with ParameterFlags constants in
|
||||
|
||||
@@ -399,12 +399,11 @@ void FfiCallbackMetadata::EnsureOnlyTriviallyImmutableValuesInClosure(
|
||||
ValidateTriviallyImmutabilityOfAnObject(zone, &obj, context.At(i));
|
||||
}
|
||||
|
||||
if (!function.does_close_over_only_final_and_shared_vars()) {
|
||||
if (!function.captures_only_final_not_late_vars()) {
|
||||
const String& error = String::Handle(
|
||||
zone,
|
||||
String::New(
|
||||
"Only final and 'vm:shared' variables can be captured by isolate "
|
||||
"group callbacks."));
|
||||
zone, String::New(
|
||||
"Only final not-late variables can be captured by isolate "
|
||||
"group callbacks."));
|
||||
Exceptions::ThrowArgumentError(error);
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
@@ -8605,22 +8605,20 @@ void Function::set_awaiter_link(Function::AwaiterLink link) const {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
bool Function::does_close_over_only_final_and_shared_vars() const {
|
||||
bool Function::captures_only_final_not_late_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();
|
||||
return ClosureData::Cast(obj).captures_only_final_not_late_vars();
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void Function::set_does_close_over_only_final_and_shared_vars(
|
||||
bool value) const {
|
||||
void Function::set_captures_only_final_not_late_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);
|
||||
ClosureData::Cast(obj).set_captures_only_final_not_late_vars(value);
|
||||
return;
|
||||
}
|
||||
UNREACHABLE();
|
||||
@@ -12208,13 +12206,12 @@ void ClosureData::set_awaiter_link(Function::AwaiterLink link) const {
|
||||
link.index);
|
||||
}
|
||||
|
||||
bool ClosureData::does_close_over_only_final_and_shared_vars() const {
|
||||
bool ClosureData::captures_only_final_not_late_vars() const {
|
||||
return untag()
|
||||
->packed_fields_.Read<UntaggedClosureData::CapturesOnlySharedFields>();
|
||||
}
|
||||
|
||||
void ClosureData::set_does_close_over_only_final_and_shared_vars(
|
||||
bool value) const {
|
||||
void ClosureData::set_captures_only_final_not_late_vars(bool value) const {
|
||||
untag()->packed_fields_.Update<UntaggedClosureData::CapturesOnlySharedFields>(
|
||||
value);
|
||||
}
|
||||
|
||||
+4
-4
@@ -3321,8 +3321,8 @@ 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;
|
||||
void set_captures_only_final_not_late_vars(bool value) const;
|
||||
bool captures_only_final_not_late_vars() const;
|
||||
|
||||
// Enclosing function of this local function.
|
||||
FunctionPtr parent_function() const;
|
||||
@@ -4417,8 +4417,8 @@ 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;
|
||||
bool captures_only_final_not_late_vars() const;
|
||||
void set_captures_only_final_not_late_vars(bool value) const;
|
||||
|
||||
// Enclosing function of this local function.
|
||||
PRECOMPILER_WSR_FIELD_DECLARATION(Function, parent_function)
|
||||
|
||||
@@ -462,7 +462,7 @@ ContextScopePtr LocalScope::PreserveOuterScope(
|
||||
|
||||
LocalVariable* awaiter_link = nullptr;
|
||||
|
||||
bool does_capture_only_final_and_shared_vars = true;
|
||||
bool captures_only_final_not_late_vars = true;
|
||||
|
||||
// Create a descriptor for each referenced captured variable of enclosing
|
||||
// functions to preserve its name and its context allocation information.
|
||||
@@ -505,8 +505,9 @@ ContextScopePtr LocalScope::PreserveOuterScope(
|
||||
|
||||
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;
|
||||
// late final variables are not allowed, only final are.
|
||||
if (!variable->is_final() || variable->is_late()) {
|
||||
captures_only_final_not_late_vars = false;
|
||||
}
|
||||
|
||||
captured_idx++;
|
||||
@@ -533,8 +534,8 @@ ContextScopePtr LocalScope::PreserveOuterScope(
|
||||
}
|
||||
|
||||
if (!function.IsNull()) {
|
||||
function.set_does_close_over_only_final_and_shared_vars(
|
||||
does_capture_only_final_and_shared_vars);
|
||||
function.set_captures_only_final_not_late_vars(
|
||||
captures_only_final_not_late_vars);
|
||||
}
|
||||
|
||||
return context_scope.ptr();
|
||||
|
||||
@@ -33,13 +33,14 @@ Future<void> testCapturedLocalVarPragmaVmShared() async {
|
||||
@pragma('vm:shared')
|
||||
// ignore: unused_local_variable
|
||||
int foo_result = 42;
|
||||
final callback = NativeCallable<CallbackNativeType>.isolateGroupBound((
|
||||
int a,
|
||||
int b,
|
||||
) {
|
||||
foo_result += (a * b);
|
||||
});
|
||||
callback.close();
|
||||
Expect.throws(() {
|
||||
final callback = NativeCallable<CallbackNativeType>.isolateGroupBound((
|
||||
int a,
|
||||
int b,
|
||||
) {
|
||||
foo_result += (a * b);
|
||||
});
|
||||
}, (e) => e.toString().contains("Only final not-late variables can be"));
|
||||
}
|
||||
|
||||
Future<void> testCapturedLocalVarFinal() async {
|
||||
|
||||
@@ -22,73 +22,51 @@ import "package:expect/expect.dart";
|
||||
|
||||
import "run_isolate_group_run_test.dart" deferred as lib1;
|
||||
|
||||
var foo = 42;
|
||||
var foo_no_initializer;
|
||||
main(List<String> args) {
|
||||
testUpdateSharedVar();
|
||||
testReturnsConstant();
|
||||
testReturnsList();
|
||||
testReturnsNotSharedFinal();
|
||||
testUpdateSharedVarWithNoInitializer();
|
||||
|
||||
@pragma('vm:shared')
|
||||
var shared_foo_no_initializer;
|
||||
testFailToAccessNotSharedVarWithInitializer();
|
||||
testFailToAccessNotSharedVarWithoutInitializer();
|
||||
|
||||
@pragma('vm:shared')
|
||||
final foo_final = 1234;
|
||||
testFailToCaptureLateFinalVar();
|
||||
testCapturesFinalNotSharedVar();
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
updateFoo() {
|
||||
foo = 56;
|
||||
}
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
updateFooNoInitializer() {
|
||||
foo_no_initializer = 78;
|
||||
}
|
||||
|
||||
class Baz {
|
||||
static late final foo;
|
||||
}
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
bar() {
|
||||
Baz.foo = 42;
|
||||
testUpdateNotSharedStaticField();
|
||||
testUpdateSharedStringStaticVar();
|
||||
|
||||
testClosure();
|
||||
|
||||
testFailToPrint();
|
||||
|
||||
testFailToIsolateGroupRunSyncThrows();
|
||||
testIsolateCurrent();
|
||||
testFailToIsolateExit();
|
||||
testFailToIsolateSpawn();
|
||||
testStringMethodTearoff();
|
||||
testListMethodTearoff(args);
|
||||
|
||||
testFailToReceivePort();
|
||||
testFailToDeferredLibrary();
|
||||
testFailToEnvironment();
|
||||
|
||||
testUserTag();
|
||||
testDoubleToString();
|
||||
testBase64Decoder();
|
||||
testRandom();
|
||||
testEncoding();
|
||||
|
||||
print("All tests completed :)");
|
||||
}
|
||||
|
||||
///
|
||||
@pragma('vm:shared')
|
||||
var list_length = 0;
|
||||
|
||||
@pragma('vm:shared')
|
||||
String string_foo = "";
|
||||
|
||||
@pragma('vm:shared')
|
||||
SendPort? sp;
|
||||
|
||||
StringMethodTearoffTest() {
|
||||
@pragma('vm:shared')
|
||||
final stringTearoff = "abc".toString;
|
||||
IsolateGroup.runSync(() {
|
||||
stringTearoff;
|
||||
});
|
||||
}
|
||||
|
||||
ListMethodTearoffTest(List<String> args) {
|
||||
final listTearoff = args.insert;
|
||||
Expect.throws(
|
||||
() {
|
||||
IsolateGroup.runSync(() {
|
||||
listTearoff;
|
||||
});
|
||||
},
|
||||
(e) =>
|
||||
e is ArgumentError && e.toString().contains("Only trivially-immutable"),
|
||||
);
|
||||
}
|
||||
|
||||
thefun() {}
|
||||
|
||||
@pragma('vm:shared')
|
||||
String default_tag = "";
|
||||
|
||||
@pragma('vm:shared')
|
||||
double pi = 3.14159;
|
||||
|
||||
main(List<String> args) {
|
||||
void testUpdateSharedVar() {
|
||||
IsolateGroup.runSync(() {
|
||||
final l = <int>[];
|
||||
for (int i = 0; i < 100; i++) {
|
||||
@@ -97,67 +75,35 @@ main(List<String> args) {
|
||||
list_length = l.length;
|
||||
});
|
||||
Expect.equals(100, list_length);
|
||||
}
|
||||
|
||||
Expect.equals(42, IsolateGroup.runSync(() => 42));
|
||||
|
||||
Expect.listEquals([1, 2, 3], IsolateGroup.runSync(() => [1, 2, 3]));
|
||||
///
|
||||
@pragma('vm:shared')
|
||||
final foo_final = 1234;
|
||||
|
||||
void testReturnsNotSharedFinal() {
|
||||
Expect.equals(1234, IsolateGroup.runSync(() => foo_final));
|
||||
}
|
||||
|
||||
IsolateGroup.runSync(() {
|
||||
shared_foo_no_initializer = 2345;
|
||||
});
|
||||
Expect.equals(2345, IsolateGroup.runSync(() => shared_foo_no_initializer));
|
||||
///
|
||||
void testReturnsConstant() {
|
||||
Expect.equals(42, IsolateGroup.runSync(() => 42));
|
||||
}
|
||||
|
||||
Expect.throws(
|
||||
() {
|
||||
IsolateGroup.runSync(() {
|
||||
throw "error";
|
||||
});
|
||||
},
|
||||
(e) => e == "error",
|
||||
'Expect thrown error',
|
||||
);
|
||||
///
|
||||
void testReturnsList() {
|
||||
Expect.listEquals([1, 2, 3], IsolateGroup.runSync(() => [1, 2, 3]));
|
||||
}
|
||||
|
||||
// Documenting current limitations.
|
||||
Expect.notEquals(() {
|
||||
IsolateGroup.runSync(() {
|
||||
return Isolate.current;
|
||||
});
|
||||
}, Isolate.current);
|
||||
///
|
||||
var foo_no_initializer;
|
||||
|
||||
Expect.throws(
|
||||
() {
|
||||
IsolateGroup.runSync(() {
|
||||
print('42');
|
||||
});
|
||||
},
|
||||
(e) => e is Error && e.toString().contains("AccessError"),
|
||||
'Expect error printing',
|
||||
);
|
||||
|
||||
updateFoo();
|
||||
Expect.throws(
|
||||
() {
|
||||
IsolateGroup.runSync(() {
|
||||
return foo;
|
||||
});
|
||||
},
|
||||
(e) => e is Error && e.toString().contains("AccessError"),
|
||||
'Expect error accessing',
|
||||
);
|
||||
|
||||
Expect.throws(
|
||||
() {
|
||||
IsolateGroup.runSync(() {
|
||||
foo = 123;
|
||||
});
|
||||
},
|
||||
(e) => e is Error && e.toString().contains("AccessError"),
|
||||
'Expect error accessing',
|
||||
);
|
||||
Expect.equals(56, foo);
|
||||
@pragma('vm:never-inline')
|
||||
updateFooNoInitializer() {
|
||||
foo_no_initializer = 78;
|
||||
}
|
||||
|
||||
void testFailToAccessNotSharedVarWithoutInitializer() {
|
||||
updateFooNoInitializer();
|
||||
Expect.throws(
|
||||
() {
|
||||
@@ -179,34 +125,156 @@ main(List<String> args) {
|
||||
'Expect error accessing',
|
||||
);
|
||||
Expect.equals(78, foo_no_initializer);
|
||||
}
|
||||
|
||||
{
|
||||
bar();
|
||||
Expect.equals(42, Baz.foo);
|
||||
}
|
||||
///
|
||||
void testFailToCaptureLateFinalVar() {
|
||||
late final int late_final_var;
|
||||
late_final_var = 12;
|
||||
Expect.throws(() {
|
||||
IsolateGroup.runSync(() {
|
||||
return late_final_var;
|
||||
});
|
||||
}, (e) => e is Error && e.toString().contains("Only final"));
|
||||
}
|
||||
|
||||
IsolateGroup.runSync(() {
|
||||
string_foo = "foo bar";
|
||||
});
|
||||
Expect.equals("foo bar", string_foo);
|
||||
///
|
||||
@pragma('vm:never-inline')
|
||||
calculateTwelve() => 12;
|
||||
|
||||
void testCapturesFinalNotSharedVar() {
|
||||
final int late_final_var = calculateTwelve();
|
||||
Expect.equals(late_final_var, IsolateGroup.runSync(() => late_final_var));
|
||||
}
|
||||
|
||||
///
|
||||
var foo = 42;
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
updateFoo() {
|
||||
foo = 56;
|
||||
}
|
||||
|
||||
void testFailToAccessNotSharedVarWithInitializer() {
|
||||
updateFoo();
|
||||
Expect.throws(
|
||||
() {
|
||||
IsolateGroup.runSync(() {
|
||||
return foo;
|
||||
});
|
||||
},
|
||||
(e) => e is Error && e.toString().contains("AccessError"),
|
||||
'Expect error accessing',
|
||||
);
|
||||
|
||||
Expect.throws(
|
||||
() {
|
||||
IsolateGroup.runSync(() {
|
||||
ReceivePort();
|
||||
foo = 123;
|
||||
});
|
||||
},
|
||||
(e) =>
|
||||
e is ArgumentError &&
|
||||
e.toString().contains("Only available when running in context"),
|
||||
(e) => e is Error && e.toString().contains("AccessError"),
|
||||
'Expect error accessing',
|
||||
);
|
||||
Expect.equals(56, foo);
|
||||
}
|
||||
|
||||
///
|
||||
@pragma('vm:shared')
|
||||
var shared_foo_no_initializer;
|
||||
|
||||
void testUpdateSharedVarWithNoInitializer() {
|
||||
IsolateGroup.runSync(() {
|
||||
shared_foo_no_initializer = 2345;
|
||||
});
|
||||
Expect.equals(2345, IsolateGroup.runSync(() => shared_foo_no_initializer));
|
||||
}
|
||||
|
||||
///
|
||||
class Baz {
|
||||
static late final foo;
|
||||
}
|
||||
|
||||
@pragma('vm:never-inline')
|
||||
updateBazFoo() {
|
||||
Baz.foo = 42;
|
||||
}
|
||||
|
||||
void testUpdateNotSharedStaticField() {
|
||||
updateBazFoo();
|
||||
Expect.equals(42, Baz.foo);
|
||||
}
|
||||
|
||||
///
|
||||
@pragma('vm:shared')
|
||||
String string_foo = "";
|
||||
|
||||
void testUpdateSharedStringStaticVar() {
|
||||
IsolateGroup.runSync(() {
|
||||
string_foo = "foo bar";
|
||||
});
|
||||
Expect.equals("foo bar", string_foo);
|
||||
}
|
||||
|
||||
///
|
||||
@pragma('vm:never-inline')
|
||||
@pragma('vm:shared')
|
||||
var closure = () {
|
||||
return 42;
|
||||
};
|
||||
void testClosure() {
|
||||
final result = IsolateGroup.runSync(() {
|
||||
return closure();
|
||||
});
|
||||
Expect.equals(42, result);
|
||||
}
|
||||
|
||||
///
|
||||
void testFailToPrint() {
|
||||
Expect.throws(
|
||||
() {
|
||||
IsolateGroup.runSync(() {
|
||||
print('42');
|
||||
});
|
||||
},
|
||||
(e) => e is Error && e.toString().contains("AccessError"),
|
||||
'Expect error printing',
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
void testFailToIsolateGroupRunSyncThrows() {
|
||||
Expect.throws(
|
||||
() {
|
||||
IsolateGroup.runSync(() {
|
||||
throw "error";
|
||||
});
|
||||
},
|
||||
(e) => e == "error",
|
||||
'Expect thrown error',
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
void testIsolateCurrent() {
|
||||
Expect.notEquals(() {
|
||||
IsolateGroup.runSync(() {
|
||||
return Isolate.current;
|
||||
});
|
||||
}, Isolate.current);
|
||||
}
|
||||
|
||||
///
|
||||
void testFailToIsolateExit() {
|
||||
Expect.throws(() {
|
||||
IsolateGroup.runSync(() {
|
||||
Isolate.exit();
|
||||
});
|
||||
}, (e) => e.toString().contains("Attempt to access isolate static field"));
|
||||
}
|
||||
|
||||
///
|
||||
void testFailToIsolateSpawn() {
|
||||
Expect.throws(() {
|
||||
IsolateGroup.runSync(() {
|
||||
Isolate.spawn((_) {}, null);
|
||||
@@ -218,44 +286,96 @@ main(List<String> args) {
|
||||
Isolate.spawnUri(Uri.parse("http://127.0.0.1"), [], (_) {});
|
||||
});
|
||||
}, (e) => e.toString().contains("Attempt to access isolate static field"));
|
||||
}
|
||||
|
||||
StringMethodTearoffTest();
|
||||
ListMethodTearoffTest(args);
|
||||
///
|
||||
testStringMethodTearoff() {
|
||||
@pragma('vm:shared')
|
||||
final stringTearoff = "abc".toString;
|
||||
IsolateGroup.runSync(() {
|
||||
stringTearoff;
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
final rp = ReceivePort();
|
||||
Expect.throws(
|
||||
() {
|
||||
IsolateGroup.runSync(() {
|
||||
sp = rp.sendPort;
|
||||
});
|
||||
},
|
||||
(e) =>
|
||||
e is ArgumentError &&
|
||||
e.toString().contains("Only trivially-immutable"),
|
||||
);
|
||||
rp.close();
|
||||
}
|
||||
///
|
||||
testListMethodTearoff(List<String> args) {
|
||||
final listTearoff = args.insert;
|
||||
Expect.throws(
|
||||
() {
|
||||
IsolateGroup.runSync(() {
|
||||
listTearoff;
|
||||
});
|
||||
},
|
||||
(e) =>
|
||||
e is ArgumentError && e.toString().contains("Only trivially-immutable"),
|
||||
);
|
||||
}
|
||||
|
||||
// deferred libraries can't be used from isolate group callbacks.
|
||||
///
|
||||
@pragma('vm:shared')
|
||||
SendPort? sp;
|
||||
|
||||
void testFailToReceivePort() {
|
||||
Expect.throws(
|
||||
() {
|
||||
IsolateGroup.runSync(() {
|
||||
ReceivePort();
|
||||
});
|
||||
},
|
||||
(e) =>
|
||||
e is ArgumentError &&
|
||||
e.toString().contains("Only available when running in context"),
|
||||
);
|
||||
|
||||
final rp = ReceivePort();
|
||||
Expect.throws(
|
||||
() {
|
||||
IsolateGroup.runSync(() {
|
||||
sp = rp.sendPort;
|
||||
});
|
||||
},
|
||||
(e) =>
|
||||
e is ArgumentError && e.toString().contains("Only trivially-immutable"),
|
||||
);
|
||||
rp.close();
|
||||
}
|
||||
|
||||
///
|
||||
thefun() {}
|
||||
|
||||
void testFailToDeferredLibrary() {
|
||||
Expect.throws(() {
|
||||
IsolateGroup.runSync(() {
|
||||
lib1.thefun();
|
||||
});
|
||||
}, (e) => e is ArgumentError && e.toString().contains("Only available when"));
|
||||
}
|
||||
|
||||
// environment can't be accessed from isolate group callbacks.
|
||||
///
|
||||
void testFailToEnvironment() {
|
||||
Expect.throws(() {
|
||||
IsolateGroup.runSync(() {
|
||||
new bool.hasEnvironment("Anything");
|
||||
});
|
||||
}, (e) => e is ArgumentError && e.toString().contains("Only available when"));
|
||||
}
|
||||
|
||||
///
|
||||
@pragma('vm:shared')
|
||||
String default_tag = "";
|
||||
|
||||
void testUserTag() {
|
||||
IsolateGroup.runSync(() {
|
||||
default_tag = UserTag.defaultTag.toString();
|
||||
});
|
||||
Expect.notEquals("", default_tag);
|
||||
}
|
||||
|
||||
///
|
||||
@pragma('vm:shared')
|
||||
double pi = 3.14159;
|
||||
|
||||
void testDoubleToString() {
|
||||
final result = IsolateGroup.runSync(() {
|
||||
return pi.toString();
|
||||
});
|
||||
@@ -264,18 +384,27 @@ main(List<String> args) {
|
||||
return identical(pi.toString(), pi.toString());
|
||||
});
|
||||
Expect.isTrue(resultIdentical);
|
||||
}
|
||||
|
||||
///
|
||||
void testBase64Decoder() {
|
||||
Expect.listEquals(
|
||||
"abcdefghijklmnopqrstuvwxyz".codeUnits,
|
||||
IsolateGroup.runSync(() {
|
||||
return Base64Decoder().convert("YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=");
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
void testRandom() {
|
||||
IsolateGroup.runSync(() {
|
||||
Random().nextInt(10);
|
||||
});
|
||||
}
|
||||
|
||||
///
|
||||
void testEncoding() {
|
||||
Expect.listEquals(
|
||||
[0x31, 0x32, 0x33, 0x61, 0x62, 0x63],
|
||||
IsolateGroup.runSync(
|
||||
@@ -290,6 +419,4 @@ main(List<String> args) {
|
||||
utf8,
|
||||
IsolateGroup.runSync(() => Encoding.getByName("utf-8")),
|
||||
);
|
||||
|
||||
print("All tests completed :)");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user