mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 15:57:17 +00:00
[vm/isolates] Include retaining path into an illegal send argument exception.
The exception contains retaining path that looks like this: ``` Invalid argument(s): Illegal argument in isolate message: (object extends NativeWrapper - Library:'dart:io' Class: _RandomAccessFileOpsImpl@14069316) <- Library:'dart:io' Class: _RandomAccessFile@14069316 <- Library:'file:///vm/dart/isolates/send_unsupported_objects_test.dart' Class: SomeLog <- Library:'file:///vm/dart/isolates/send_unsupported_objects_test.dart' Class: SomeState <- Class: Context <- Library:'dart:core' Class: _Closure@0150898 ``` BUG=https://github.com/dart-lang/sdk/issues/51115 BUG=https://github.com/dart-lang/sdk/issues/48592 TEST=send_unsupported_objects_test Change-Id: I022e693adccf43a7d2c95e1c7283fd7f210cf1d7 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/280523 Commit-Queue: Alexander Aprelev <aam@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Reviewed-by: Ryan Macnak <rmacnak@google.com>
This commit is contained in:
parent
1f6d4ae1a8
commit
4f30b5c9b5
|
@ -278,7 +278,13 @@ static ObjectPtr ValidateMessageObject(Zone* zone,
|
|||
|
||||
const Array& args = Array::Handle(zone, Array::New(3));
|
||||
args.SetAt(0, illegal_object);
|
||||
args.SetAt(2, String::Handle(zone, String::New(exception_message)));
|
||||
args.SetAt(2, String::Handle(
|
||||
zone, String::NewFormatted(
|
||||
"%s%s",
|
||||
FindRetainingPath(
|
||||
zone, isolate, obj, illegal_object,
|
||||
TraversalRules::kInternalToIsolateGroup),
|
||||
exception_message)));
|
||||
const Object& exception = Object::Handle(
|
||||
zone, Exceptions::Create(Exceptions::kArgumentValue, args));
|
||||
return UnhandledException::New(Instance::Cast(exception),
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
// 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:async';
|
||||
import 'dart:isolate';
|
||||
|
||||
import "package:async_helper/async_helper.dart";
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
import 'send_unsupported_objects_test.dart';
|
||||
|
||||
const NESTED_DEPTH = 500;
|
||||
Future<List> buildNestedList(List<dynamic> list, int level) async {
|
||||
final fu = level == NESTED_DEPTH ? Fu.unsendable("$level") : Fu("$level");
|
||||
final newlist = <dynamic>[list, fu];
|
||||
if (--level == 0) {
|
||||
return newlist;
|
||||
}
|
||||
return await buildNestedList(newlist, level);
|
||||
}
|
||||
|
||||
main() async {
|
||||
asyncStart();
|
||||
try {
|
||||
final nestedList = await buildNestedList(<dynamic>[], NESTED_DEPTH);
|
||||
// Send closure capturing nestedList
|
||||
await Isolate.spawn((arg) {
|
||||
arg();
|
||||
}, () {
|
||||
print('$nestedList');
|
||||
});
|
||||
} catch (e) {
|
||||
checkForRetainingPath(e, <String>[
|
||||
'NativeWrapper',
|
||||
'Baz',
|
||||
'Fu',
|
||||
'Closure',
|
||||
]);
|
||||
|
||||
final msg = e.toString();
|
||||
Expect.isTrue(msg.split('\n').length > NESTED_DEPTH * 2);
|
||||
asyncEnd();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// 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:async';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import "package:async_helper/async_helper.dart";
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
import 'send_unsupported_objects_test.dart';
|
||||
|
||||
worker(SendPort sp) async {
|
||||
try {
|
||||
Isolate.exit(sp, Fu.unsendable('fu'));
|
||||
} catch (e) {
|
||||
checkForRetainingPath(e, <String>[
|
||||
'NativeWrapper',
|
||||
'Baz',
|
||||
'Fu',
|
||||
]);
|
||||
sp.send(true);
|
||||
}
|
||||
}
|
||||
|
||||
main() async {
|
||||
asyncStart();
|
||||
final rp = ReceivePort();
|
||||
await Isolate.spawn(worker, rp.sendPort);
|
||||
Expect.isTrue(await rp.first);
|
||||
asyncEnd();
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// 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:async';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:async_helper/async_helper.dart';
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
import 'send_unsupported_objects_test.dart';
|
||||
|
||||
main() async {
|
||||
asyncStart();
|
||||
|
||||
final fu = Fu.unsendable('fu');
|
||||
try {
|
||||
// Pass a closure that captures [fu]
|
||||
await Isolate.spawn((arg) {
|
||||
arg();
|
||||
}, () {
|
||||
print('${fu.label}');
|
||||
Expect.fail('This closure should fail to be sent, shouldn\'t be called');
|
||||
});
|
||||
} catch (e) {
|
||||
checkForRetainingPath(e, <String>[
|
||||
'NativeWrapper',
|
||||
'Baz',
|
||||
'Fu',
|
||||
'Context',
|
||||
'Closure',
|
||||
]);
|
||||
asyncEnd();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
// 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.
|
||||
|
||||
// Verifies retaining path in error message for spawnUri'ed worker attempt
|
||||
// to send regular class instance.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:nativewrappers';
|
||||
|
||||
import 'package:async_helper/async_helper.dart';
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
import 'send_unsupported_objects_test.dart';
|
||||
|
||||
class ConstFoo {
|
||||
const ConstFoo(this.name);
|
||||
final String name;
|
||||
}
|
||||
|
||||
Future<void> main(args, message) async {
|
||||
if (message == null) {
|
||||
final receivePort = ReceivePort();
|
||||
final isolate = await Isolate.spawnUri(
|
||||
Platform.script, <String>['worker'], <SendPort>[receivePort.sendPort],
|
||||
errorsAreFatal: true);
|
||||
final result = await receivePort.first;
|
||||
Expect.equals('done', result);
|
||||
return;
|
||||
}
|
||||
|
||||
Expect.equals('worker', args[0]);
|
||||
final SendPort sendPort = message[0] as SendPort;
|
||||
Expect.throws(() {
|
||||
sendPort.send(<dynamic>[
|
||||
<dynamic>[
|
||||
<dynamic>[const ConstFoo("42")],
|
||||
],
|
||||
]);
|
||||
}, (e) {
|
||||
print(e);
|
||||
checkForRetainingPath(e, <String>['ConstFoo']);
|
||||
|
||||
final msg = e.toString();
|
||||
Expect.equals(3, msg.split('\n').where((s) => s.contains('_List')).length);
|
||||
return true;
|
||||
});
|
||||
sendPort.send('done');
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
// 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:async';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:nativewrappers';
|
||||
|
||||
import 'package:async_helper/async_helper.dart';
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
import 'send_unsupported_objects_init_isolate_test.dart';
|
||||
|
||||
class Foo {
|
||||
int i = 42;
|
||||
}
|
||||
|
||||
class Bar {
|
||||
Foo foo = Foo();
|
||||
}
|
||||
|
||||
class NativeClass extends NativeFieldWrapperClass1 {}
|
||||
|
||||
class Baz {
|
||||
@pragma('vm:entry-point') // prevent tree-shaking of the field.
|
||||
NativeClass? nativeClass;
|
||||
Baz();
|
||||
}
|
||||
|
||||
class Fu {
|
||||
String label;
|
||||
Bar bar = Bar();
|
||||
Baz baz = Baz();
|
||||
|
||||
Fu(this.label);
|
||||
Fu.unsendable(this.label) {
|
||||
baz.nativeClass = NativeClass();
|
||||
}
|
||||
}
|
||||
|
||||
void checkForRetainingPath(Object? e, List<String> list) {
|
||||
Expect.isTrue(e is ArgumentError);
|
||||
final msg = e.toString();
|
||||
list.forEach((s) {
|
||||
Expect.contains(s, msg);
|
||||
});
|
||||
}
|
||||
|
||||
main() async {
|
||||
asyncStart();
|
||||
final rp = ReceivePort();
|
||||
try {
|
||||
rp.sendPort.send(Fu.unsendable('fu'));
|
||||
} catch (e) {
|
||||
checkForRetainingPath(e, <String>[
|
||||
'NativeWrapper',
|
||||
'Baz',
|
||||
'Fu',
|
||||
]);
|
||||
}
|
||||
rp.close();
|
||||
asyncEnd();
|
||||
}
|
|
@ -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.
|
||||
|
||||
// @dart = 2.9
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:isolate';
|
||||
|
||||
import "package:async_helper/async_helper.dart";
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
import 'send_unsupported_objects_test.dart';
|
||||
|
||||
const NESTED_DEPTH = 500;
|
||||
Future<List> buildNestedList(List<dynamic> list, int level) async {
|
||||
final fu = level == NESTED_DEPTH ? Fu.unsendable("$level") : Fu("$level");
|
||||
final newlist = <dynamic>[list, fu];
|
||||
if (--level == 0) {
|
||||
return newlist;
|
||||
}
|
||||
return await buildNestedList(newlist, level);
|
||||
}
|
||||
|
||||
main() async {
|
||||
asyncStart();
|
||||
try {
|
||||
final nestedList = await buildNestedList(<dynamic>[], NESTED_DEPTH);
|
||||
// Send closure capturing nestedList
|
||||
await Isolate.spawn((arg) {
|
||||
arg();
|
||||
}, () {
|
||||
print('$nestedList');
|
||||
});
|
||||
} catch (e) {
|
||||
checkForRetainingPath(e, <String>[
|
||||
'NativeWrapper',
|
||||
'Baz',
|
||||
'Fu',
|
||||
'Closure',
|
||||
]);
|
||||
|
||||
final msg = e.toString();
|
||||
Expect.isTrue(msg.split('\n').length > NESTED_DEPTH * 2);
|
||||
asyncEnd();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// 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:async';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import "package:async_helper/async_helper.dart";
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
import 'send_unsupported_objects_test.dart';
|
||||
|
||||
worker(SendPort sp) async {
|
||||
try {
|
||||
Isolate.exit(sp, Fu.unsendable('fu'));
|
||||
} catch (e) {
|
||||
checkForRetainingPath(e, <String>[
|
||||
'NativeWrapper',
|
||||
'Baz',
|
||||
'Fu',
|
||||
]);
|
||||
sp.send(true);
|
||||
}
|
||||
}
|
||||
|
||||
main() async {
|
||||
asyncStart();
|
||||
final rp = ReceivePort();
|
||||
await Isolate.spawn(worker, rp.sendPort);
|
||||
Expect.isTrue(await rp.first);
|
||||
asyncEnd();
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// 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:async';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:async_helper/async_helper.dart';
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
import 'send_unsupported_objects_test.dart';
|
||||
|
||||
main() async {
|
||||
asyncStart();
|
||||
|
||||
final fu = Fu.unsendable('fu');
|
||||
try {
|
||||
// Pass a closure that captures [fu]
|
||||
await Isolate.spawn((arg) {
|
||||
arg();
|
||||
}, () {
|
||||
print('${fu.label}');
|
||||
Expect.fail('This closure should fail to be sent, shouldn\'t be called');
|
||||
});
|
||||
} catch (e) {
|
||||
checkForRetainingPath(e, <String>[
|
||||
'NativeWrapper',
|
||||
'Baz',
|
||||
'Fu',
|
||||
'Context',
|
||||
'Closure',
|
||||
]);
|
||||
asyncEnd();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
// 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.
|
||||
|
||||
// Verifies retaining path in error message for spawnUri'ed worker attempt
|
||||
// to send regular class instance.
|
||||
|
||||
// @dart = 2.9
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:nativewrappers';
|
||||
|
||||
import 'package:async_helper/async_helper.dart';
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
import 'send_unsupported_objects_test.dart';
|
||||
|
||||
class ConstFoo {
|
||||
const ConstFoo(this.name);
|
||||
final String name;
|
||||
}
|
||||
|
||||
Future<void> main(args, message) async {
|
||||
if (message == null) {
|
||||
final receivePort = ReceivePort();
|
||||
final isolate = await Isolate.spawnUri(
|
||||
Platform.script, <String>['worker'], <SendPort>[receivePort.sendPort],
|
||||
errorsAreFatal: true);
|
||||
final result = await receivePort.first;
|
||||
Expect.equals('done', result);
|
||||
return;
|
||||
}
|
||||
|
||||
Expect.equals('worker', args[0]);
|
||||
final SendPort sendPort = message[0] as SendPort;
|
||||
Expect.throws(() {
|
||||
sendPort.send(<dynamic>[
|
||||
<dynamic>[
|
||||
<dynamic>[const ConstFoo("42")]
|
||||
]
|
||||
]);
|
||||
}, (e) {
|
||||
checkForRetainingPath(e, <String>['ConstFoo']);
|
||||
|
||||
final msg = e.toString();
|
||||
Expect.equals(3, msg.split('\n').where((s) => s.contains('_List')).length);
|
||||
return true;
|
||||
});
|
||||
sendPort.send('done');
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// 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:async';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:nativewrappers';
|
||||
|
||||
import 'package:async_helper/async_helper.dart';
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
import 'send_unsupported_objects_init_isolate_test.dart';
|
||||
|
||||
class Foo {
|
||||
int i = 42;
|
||||
}
|
||||
|
||||
class Bar {
|
||||
Foo foo = Foo();
|
||||
}
|
||||
|
||||
class NativeClass extends NativeFieldWrapperClass1 {}
|
||||
|
||||
class Baz {
|
||||
@pragma('vm:entry-point') // prevent tree-shaking of the field.
|
||||
NativeClass nativeClass;
|
||||
Baz();
|
||||
}
|
||||
|
||||
class Fu {
|
||||
String label;
|
||||
Bar bar = Bar();
|
||||
Baz baz = Baz();
|
||||
|
||||
Fu(this.label);
|
||||
Fu.unsendable(this.label) {
|
||||
baz.nativeClass = NativeClass();
|
||||
}
|
||||
}
|
||||
|
||||
void checkForRetainingPath(Object e, List<String> list) {
|
||||
Expect.isTrue(e is ArgumentError);
|
||||
final msg = e.toString();
|
||||
list.forEach((s) {
|
||||
Expect.contains(s, msg);
|
||||
});
|
||||
}
|
||||
|
||||
main() async {
|
||||
asyncStart();
|
||||
final rp = ReceivePort();
|
||||
try {
|
||||
rp.sendPort.send(Fu.unsendable('fu'));
|
||||
} catch (e) {
|
||||
checkForRetainingPath(e, <String>[
|
||||
'NativeWrapper',
|
||||
'Baz',
|
||||
'Fu',
|
||||
]);
|
||||
}
|
||||
rp.close();
|
||||
asyncEnd();
|
||||
}
|
|
@ -250,7 +250,7 @@ class MessageSerializer : public BaseSerializer {
|
|||
|
||||
void Push(ObjectPtr object);
|
||||
|
||||
void Trace(Object* object);
|
||||
void Trace(const Object& root, Object* object);
|
||||
|
||||
void IllegalObject(const Object& object, const char* message);
|
||||
|
||||
|
@ -2803,7 +2803,7 @@ void ApiMessageSerializer::Push(Dart_CObject* object) {
|
|||
}
|
||||
}
|
||||
|
||||
void MessageSerializer::Trace(Object* object) {
|
||||
void MessageSerializer::Trace(const Object& root, Object* object) {
|
||||
intptr_t cid;
|
||||
bool is_canonical;
|
||||
if (!object->ptr()->IsHeapObject()) {
|
||||
|
@ -2823,39 +2823,59 @@ void MessageSerializer::Trace(Object* object) {
|
|||
}
|
||||
if (cluster == nullptr) {
|
||||
if (cid >= kNumPredefinedCids || cid == kInstanceCid) {
|
||||
IllegalObject(*object, "is a regular instance");
|
||||
// Will stomp over forward_table_new/old WeakTables, which should be ok,
|
||||
// as they are not going to be used again here.
|
||||
const char* message = OS::SCreate(
|
||||
zone_, "is a regular instance reachable via %s",
|
||||
FindRetainingPath(zone_, isolate(), root, *object,
|
||||
TraversalRules::kExternalBetweenIsolateGroups));
|
||||
IllegalObject(*object, message);
|
||||
}
|
||||
|
||||
// Keep the list in sync with the one in lib/isolate.cc
|
||||
const char* illegal_cid_string = nullptr;
|
||||
// Keep the list in sync with the one in lib/isolate.cc,
|
||||
// vm/object_graph_copy.cc
|
||||
#define ILLEGAL(type) \
|
||||
if (cid == k##type##Cid) { \
|
||||
IllegalObject(*object, "is a " #type); \
|
||||
}
|
||||
case k##type##Cid: \
|
||||
illegal_cid_string = #type; \
|
||||
break;
|
||||
|
||||
ILLEGAL(Closure)
|
||||
ILLEGAL(Finalizer)
|
||||
ILLEGAL(FinalizerEntry)
|
||||
ILLEGAL(FunctionType)
|
||||
ILLEGAL(MirrorReference)
|
||||
ILLEGAL(NativeFinalizer)
|
||||
ILLEGAL(ReceivePort)
|
||||
ILLEGAL(Record)
|
||||
ILLEGAL(RecordType)
|
||||
ILLEGAL(RegExp)
|
||||
ILLEGAL(StackTrace)
|
||||
ILLEGAL(SuspendState)
|
||||
ILLEGAL(UserTag)
|
||||
ILLEGAL(WeakProperty)
|
||||
ILLEGAL(WeakReference)
|
||||
ILLEGAL(WeakArray)
|
||||
switch (cid) {
|
||||
ILLEGAL(Closure)
|
||||
ILLEGAL(Finalizer)
|
||||
ILLEGAL(FinalizerEntry)
|
||||
ILLEGAL(FunctionType)
|
||||
ILLEGAL(MirrorReference)
|
||||
ILLEGAL(NativeFinalizer)
|
||||
ILLEGAL(ReceivePort)
|
||||
ILLEGAL(Record)
|
||||
ILLEGAL(RecordType)
|
||||
ILLEGAL(RegExp)
|
||||
ILLEGAL(StackTrace)
|
||||
ILLEGAL(SuspendState)
|
||||
ILLEGAL(UserTag)
|
||||
ILLEGAL(WeakProperty)
|
||||
ILLEGAL(WeakReference)
|
||||
ILLEGAL(WeakArray)
|
||||
|
||||
// From "dart:ffi" we handle only Pointer/DynamicLibrary specially, since
|
||||
// those are the only non-abstract classes (so we avoid checking more cids
|
||||
// here that cannot happen in reality)
|
||||
ILLEGAL(DynamicLibrary)
|
||||
ILLEGAL(Pointer)
|
||||
// From "dart:ffi" we handle only Pointer/DynamicLibrary specially, since
|
||||
// those are the only non-abstract classes (so we avoid checking more cids
|
||||
// here that cannot happen in reality)
|
||||
ILLEGAL(DynamicLibrary)
|
||||
ILLEGAL(Pointer)
|
||||
|
||||
#undef ILLEGAL
|
||||
}
|
||||
|
||||
if (illegal_cid_string != nullptr) {
|
||||
// Will stomp over forward_table_new/old WeakTables, which should be ok,
|
||||
// as they are not going to be used again here.
|
||||
const char* message = OS::SCreate(
|
||||
zone_, "is a %s reachable via %s", illegal_cid_string,
|
||||
FindRetainingPath(zone_, isolate(), root, *object,
|
||||
TraversalRules::kExternalBetweenIsolateGroups));
|
||||
IllegalObject(*object, message);
|
||||
}
|
||||
|
||||
cluster = NewClusterForClass(cid, is_canonical);
|
||||
clusters_.Add(cluster);
|
||||
|
@ -3319,7 +3339,7 @@ void MessageSerializer::Serialize(const Object& root) {
|
|||
Push(root.ptr());
|
||||
|
||||
while (stack_.length() > 0) {
|
||||
Trace(stack_.RemoveLast());
|
||||
Trace(root, stack_.RemoveLast());
|
||||
}
|
||||
|
||||
intptr_t num_objects = num_base_objects_ + num_written_objects_;
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
#include "vm/object_graph_copy.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "vm/dart_api_state.h"
|
||||
#include "vm/flags.h"
|
||||
#include "vm/heap/weak_table.h"
|
||||
|
@ -758,7 +760,8 @@ class ObjectCopyBase {
|
|||
tmp_(Object::Handle(thread->zone())),
|
||||
to_(Object::Handle(thread->zone())),
|
||||
expando_cid_(Class::GetClassId(
|
||||
thread->isolate_group()->object_store()->expando_class())) {}
|
||||
thread->isolate_group()->object_store()->expando_class())),
|
||||
exception_unexpected_object_(Object::Handle(thread->zone())) {}
|
||||
~ObjectCopyBase() {}
|
||||
|
||||
protected:
|
||||
|
@ -832,6 +835,7 @@ class ObjectCopyBase {
|
|||
"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 =
|
||||
|
@ -842,6 +846,7 @@ class ObjectCopyBase {
|
|||
"Illegal argument in isolate message: (object implements "
|
||||
"Finalizable - %s)",
|
||||
Class::Handle(class_table_->At(cid)).ToCString());
|
||||
exception_unexpected_object_ = object;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -851,6 +856,7 @@ class ObjectCopyBase {
|
|||
exception_msg_ = \
|
||||
"Illegal argument in isolate message: " \
|
||||
"(object is a " #Type ")"; \
|
||||
exception_unexpected_object_ = object; \
|
||||
return false; \
|
||||
}
|
||||
|
||||
|
@ -882,8 +888,271 @@ class ObjectCopyBase {
|
|||
intptr_t expando_cid_;
|
||||
|
||||
const char* exception_msg_ = nullptr;
|
||||
Object& exception_unexpected_object_;
|
||||
};
|
||||
|
||||
class RetainingPath {
|
||||
class Visitor : public ObjectPointerVisitor {
|
||||
public:
|
||||
Visitor(IsolateGroup* isolate_group,
|
||||
RetainingPath* retaining_path,
|
||||
MallocGrowableArray<ObjectPtr>* const working_list,
|
||||
TraversalRules traversal_rules)
|
||||
: ObjectPointerVisitor(isolate_group),
|
||||
retaining_path_(retaining_path),
|
||||
working_list_(working_list),
|
||||
traversal_rules_(traversal_rules) {}
|
||||
|
||||
void VisitObject(ObjectPtr obj) {
|
||||
if (!obj->IsHeapObject()) {
|
||||
return;
|
||||
}
|
||||
// Skip canonical objects when rules are for messages internal to
|
||||
// an isolate group. Otherwise, need to inspect canonical objects
|
||||
// as well.
|
||||
if (traversal_rules_ == TraversalRules::kInternalToIsolateGroup &&
|
||||
obj->untag()->IsCanonical()) {
|
||||
return;
|
||||
}
|
||||
if (retaining_path_->WasVisited(obj)) {
|
||||
return;
|
||||
}
|
||||
retaining_path_->MarkVisited(obj);
|
||||
working_list_->Add(obj);
|
||||
}
|
||||
|
||||
void VisitPointers(ObjectPtr* from, ObjectPtr* to) override {
|
||||
for (ObjectPtr* ptr = from; ptr <= to; ptr++) {
|
||||
VisitObject(*ptr);
|
||||
}
|
||||
}
|
||||
|
||||
void VisitCompressedPointers(uword heap_base,
|
||||
CompressedObjectPtr* from,
|
||||
CompressedObjectPtr* to) override {
|
||||
for (CompressedObjectPtr* ptr = from; ptr <= to; ptr++) {
|
||||
VisitObject(ptr->Decompress(heap_base));
|
||||
}
|
||||
}
|
||||
|
||||
RetainingPath* retaining_path_;
|
||||
MallocGrowableArray<ObjectPtr>* const working_list_;
|
||||
TraversalRules traversal_rules_;
|
||||
};
|
||||
|
||||
public:
|
||||
RetainingPath(Zone* zone,
|
||||
Isolate* isolate,
|
||||
const Object& from,
|
||||
const Object& to,
|
||||
TraversalRules traversal_rules)
|
||||
: zone_(zone),
|
||||
isolate_(isolate),
|
||||
from_(from),
|
||||
to_(to),
|
||||
traversal_rules_(traversal_rules) {
|
||||
isolate_->set_forward_table_new(new WeakTable());
|
||||
isolate_->set_forward_table_old(new WeakTable());
|
||||
}
|
||||
|
||||
~RetainingPath() {
|
||||
isolate_->set_forward_table_new(nullptr);
|
||||
isolate_->set_forward_table_old(nullptr);
|
||||
}
|
||||
|
||||
bool WasVisited(ObjectPtr object) {
|
||||
if (object->IsNewObject()) {
|
||||
return isolate_->forward_table_new()->GetValueExclusive(object) != 0;
|
||||
} else {
|
||||
return isolate_->forward_table_old()->GetValueExclusive(object) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
void MarkVisited(ObjectPtr object) {
|
||||
if (object->IsNewObject()) {
|
||||
isolate_->forward_table_new()->SetValueExclusive(object, 1);
|
||||
} else {
|
||||
isolate_->forward_table_old()->SetValueExclusive(object, 1);
|
||||
}
|
||||
}
|
||||
|
||||
const char* FindPath() {
|
||||
MallocGrowableArray<ObjectPtr>* const working_list =
|
||||
isolate_->pointers_to_verify_at_exit();
|
||||
ASSERT(working_list->length() == 0);
|
||||
|
||||
Visitor visitor(isolate_->group(), this, working_list, traversal_rules_);
|
||||
|
||||
MarkVisited(from_.ptr());
|
||||
working_list->Add(from_.ptr());
|
||||
|
||||
Thread* thread = Thread::Current();
|
||||
ClassTable* class_table = isolate_->group()->class_table();
|
||||
Closure& closure = Closure::Handle(zone_);
|
||||
Array& array = Array::Handle(zone_);
|
||||
Class& klass = Class::Handle(zone_);
|
||||
|
||||
while (!working_list->is_empty()) {
|
||||
thread->CheckForSafepoint();
|
||||
|
||||
// Keep node in the list, separated by null value so that
|
||||
// if we are to add children, children can find it in case
|
||||
// they are on retaining path.
|
||||
ObjectPtr raw = working_list->Last();
|
||||
if (raw == Object::null()) {
|
||||
// If all children of a node were processed, then skip the separator,
|
||||
working_list->RemoveLast();
|
||||
// then skip the parent since it has already been processed too.
|
||||
working_list->RemoveLast();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (raw == to_.ptr()) {
|
||||
return CollectPath(working_list);
|
||||
}
|
||||
|
||||
// Separator null object indicates children goes next in the working_list
|
||||
working_list->Add(Object::null());
|
||||
int length = working_list->length();
|
||||
|
||||
do { // This loop is here so that we can skip children processing
|
||||
const intptr_t cid = raw->GetClassId();
|
||||
|
||||
if (traversal_rules_ == TraversalRules::kInternalToIsolateGroup) {
|
||||
if (CanShareObjectAcrossIsolates(raw)) {
|
||||
break;
|
||||
}
|
||||
if (cid == kClosureCid) {
|
||||
closure ^= raw;
|
||||
// Only context has to be checked.
|
||||
working_list->Add(closure.context());
|
||||
break;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ASSERT(traversal_rules_ ==
|
||||
TraversalRules::kExternalBetweenIsolateGroups);
|
||||
// Skip classes that are illegal to send across isolate groups.
|
||||
// (keep the list in sync with message_snapshot.cc)
|
||||
bool skip = false;
|
||||
switch (cid) {
|
||||
case kClosureCid:
|
||||
case kFinalizerCid:
|
||||
case kFinalizerEntryCid:
|
||||
case kFunctionTypeCid:
|
||||
case kMirrorReferenceCid:
|
||||
case kNativeFinalizerCid:
|
||||
case kReceivePortCid:
|
||||
case kRecordCid:
|
||||
case kRecordTypeCid:
|
||||
case kRegExpCid:
|
||||
case kStackTraceCid:
|
||||
case kSuspendStateCid:
|
||||
case kUserTagCid:
|
||||
case kWeakPropertyCid:
|
||||
case kWeakReferenceCid:
|
||||
case kWeakArrayCid:
|
||||
case kDynamicLibraryCid:
|
||||
case kPointerCid:
|
||||
case kInstanceCid:
|
||||
skip = true;
|
||||
break;
|
||||
default:
|
||||
if (cid >= kNumPredefinedCids) {
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
if (skip) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cid == kArrayCid) {
|
||||
array ^= Array::RawCast(raw);
|
||||
visitor.VisitObject(array.GetTypeArguments());
|
||||
const intptr_t batch_size = (2 << 14) - 1;
|
||||
for (intptr_t i = 0; i < array.Length(); ++i) {
|
||||
ObjectPtr ptr = array.At(i);
|
||||
visitor.VisitObject(ptr);
|
||||
if ((i & batch_size) == batch_size) {
|
||||
thread->CheckForSafepoint();
|
||||
}
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
raw->untag()->VisitPointers(&visitor);
|
||||
}
|
||||
} while (false);
|
||||
|
||||
// If no children were added, remove null separator and the node.
|
||||
// If children were added, the node will be removed once last child
|
||||
// is processed, only separator null remains.
|
||||
if (working_list->length() == length) {
|
||||
RELEASE_ASSERT(working_list->RemoveLast() == Object::null());
|
||||
RELEASE_ASSERT(working_list->RemoveLast() == raw);
|
||||
}
|
||||
}
|
||||
// `to` was not found in the graph rooted in `from`, empty retaining path
|
||||
return "";
|
||||
}
|
||||
|
||||
private:
|
||||
Zone* zone_;
|
||||
Isolate* isolate_;
|
||||
const Object& from_;
|
||||
const Object& to_;
|
||||
TraversalRules traversal_rules_;
|
||||
|
||||
const char* CollectPath(MallocGrowableArray<ObjectPtr>* const working_list) {
|
||||
Object& object = Object::Handle(zone_);
|
||||
Class& klass = Class::Handle(zone_);
|
||||
Library& library = Library::Handle(zone_);
|
||||
String& library_url = String::Handle(zone_);
|
||||
const char* retaining_path = "";
|
||||
|
||||
ObjectPtr raw = to_.ptr();
|
||||
// Skip all remaining children until null-separator, so we get the parent
|
||||
do {
|
||||
do {
|
||||
raw = working_list->RemoveLast();
|
||||
} while (raw != Object::null() && raw != from_.ptr());
|
||||
if (raw == Object::null()) {
|
||||
raw = working_list->RemoveLast();
|
||||
object = raw;
|
||||
klass = object.clazz();
|
||||
library = klass.library();
|
||||
if (library.IsNull()) {
|
||||
retaining_path = OS::SCreate(zone_, "%s <- %s\n", retaining_path,
|
||||
object.ToCString());
|
||||
} else {
|
||||
library_url = library.url();
|
||||
retaining_path =
|
||||
OS::SCreate(zone_, "%s <- %s (from %s)\n", retaining_path,
|
||||
object.ToCString(), library_url.ToCString());
|
||||
}
|
||||
}
|
||||
} while (raw != from_.ptr());
|
||||
ASSERT(working_list->is_empty());
|
||||
return retaining_path;
|
||||
}
|
||||
};
|
||||
|
||||
const char* FindRetainingPath(Zone* zone_,
|
||||
Isolate* isolate,
|
||||
const Object& from,
|
||||
const Object& to,
|
||||
TraversalRules traversal_rules) {
|
||||
RetainingPath rr(zone_, isolate, from, to, traversal_rules);
|
||||
return rr.FindPath();
|
||||
}
|
||||
|
||||
class FastObjectCopyBase : public ObjectCopyBase {
|
||||
public:
|
||||
using Types = PtrTypes;
|
||||
|
@ -1642,6 +1911,8 @@ class ObjectCopy : public Base {
|
|||
Base::exception_msg_ =
|
||||
"Illegal argument in isolate message"
|
||||
" : (TransferableTypedData has been transferred already)";
|
||||
Base::exception_unexpected_object_ =
|
||||
Types::GetTransferableTypedDataPtr(from);
|
||||
return;
|
||||
}
|
||||
Base::EnqueueTransferable(from, to);
|
||||
|
@ -2045,8 +2316,18 @@ class ObjectGraphCopier : public StackResource {
|
|||
Exceptions::PropagateError(Error::Cast(result));
|
||||
UNREACHABLE();
|
||||
}
|
||||
if (result.ptr() == Marker()) {
|
||||
ASSERT(result.IsArray());
|
||||
auto& result_array = Array::Cast(result);
|
||||
if (result_array.At(0) == Marker()) {
|
||||
ASSERT(exception_msg != nullptr);
|
||||
auto& unexpected_object_ = Object::Handle(zone_, result_array.At(1));
|
||||
if (!unexpected_object_.IsNull()) {
|
||||
exception_msg =
|
||||
OS::SCreate(zone_, "%s\n%s", exception_msg,
|
||||
FindRetainingPath(
|
||||
zone_, thread_->isolate(), root, unexpected_object_,
|
||||
TraversalRules::kInternalToIsolateGroup));
|
||||
}
|
||||
ThrowException(exception_msg);
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
@ -2077,7 +2358,9 @@ class ObjectGraphCopier : public StackResource {
|
|||
if (!fast_object_copy_.CanCopyObject(tags, root.ptr())) {
|
||||
ASSERT(fast_object_copy_.exception_msg_ != nullptr);
|
||||
*exception_msg = fast_object_copy_.exception_msg_;
|
||||
return Marker();
|
||||
result_array.SetAt(0, Object::Handle(zone_, Marker()));
|
||||
result_array.SetAt(1, fast_object_copy_.exception_unexpected_object_);
|
||||
return result_array.ptr();
|
||||
}
|
||||
|
||||
// We try a fast new-space only copy first that will not use any barriers.
|
||||
|
@ -2128,7 +2411,9 @@ class ObjectGraphCopier : public StackResource {
|
|||
ASSERT(fast_object_copy_.exception_msg_ != nullptr);
|
||||
if (fast_object_copy_.exception_msg_ != kFastAllocationFailed) {
|
||||
*exception_msg = fast_object_copy_.exception_msg_;
|
||||
return Marker();
|
||||
result_array.SetAt(0, Object::Handle(zone_, Marker()));
|
||||
result_array.SetAt(1, fast_object_copy_.exception_unexpected_object_);
|
||||
return result_array.ptr();
|
||||
}
|
||||
ASSERT(fast_object_copy_.exception_msg_ == kFastAllocationFailed);
|
||||
}
|
||||
|
@ -2139,7 +2424,9 @@ class ObjectGraphCopier : public StackResource {
|
|||
(slow_object_copy_.exception_msg_ != nullptr));
|
||||
if (result.ptr() == Marker()) {
|
||||
*exception_msg = slow_object_copy_.exception_msg_;
|
||||
return Marker();
|
||||
result_array.SetAt(0, Object::Handle(zone_, Marker()));
|
||||
result_array.SetAt(1, slow_object_copy_.exception_unexpected_object_);
|
||||
return result_array.ptr();
|
||||
}
|
||||
|
||||
result_array.SetAt(0, result);
|
||||
|
|
|
@ -7,8 +7,10 @@
|
|||
|
||||
namespace dart {
|
||||
|
||||
class Isolate;
|
||||
class Object;
|
||||
class ObjectPtr;
|
||||
class Zone;
|
||||
|
||||
// Whether the object can safely be shared across isolates due to it being
|
||||
// deeply immutable.
|
||||
|
@ -29,6 +31,21 @@ bool CanShareObjectAcrossIsolates(ObjectPtr obj);
|
|||
// those objects.
|
||||
ObjectPtr CopyMutableObjectGraph(const Object& root);
|
||||
|
||||
typedef enum {
|
||||
kInternalToIsolateGroup,
|
||||
kExternalBetweenIsolateGroups,
|
||||
} TraversalRules;
|
||||
|
||||
// Returns a string representation of a retaining path from `from` to `to`,
|
||||
// blank string if `to` is not reachable from `from`.
|
||||
// Traversal doesn't follow all the object graph links, only those
|
||||
// that makes sense isolate message passing.
|
||||
const char* FindRetainingPath(Zone* zone,
|
||||
Isolate* isolate,
|
||||
const Object& from,
|
||||
const Object& to,
|
||||
TraversalRules traversal_rules);
|
||||
|
||||
} // namespace dart
|
||||
|
||||
#endif // RUNTIME_VM_OBJECT_GRAPH_COPY_H_
|
||||
|
|
Loading…
Reference in a new issue