[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:
Alexander Aprelev 2023-02-23 03:11:08 +00:00 committed by Commit Queue
parent 1f6d4ae1a8
commit 4f30b5c9b5
14 changed files with 830 additions and 35 deletions

View file

@ -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),

View file

@ -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();
}
}

View file

@ -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();
}

View file

@ -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();
}
}

View file

@ -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');
}

View file

@ -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();
}

View 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.
// @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();
}
}

View file

@ -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();
}

View file

@ -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();
}
}

View file

@ -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');
}

View file

@ -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();
}

View file

@ -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_;

View file

@ -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);

View file

@ -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_