6e987b6eb5
This replaces _requestHeapSnapshot and _getObjectByAddresses, which relied on the invalid assumption that objects don't move and the heap hasn't mutated since the initial snapshot. Updates Observatory to the new format. Adds treemap visualization of the dominator tree. Adds the ability to save and load heap snapshots to files. Removes analysis grouping references by source and target class. Removes ability to navigate from a pixel in the fragmentation view to inspector. Removes ability to query the top retaining instance from an ordinary class browser. Change-Id: Ia7781c05d43bf3ec149f8b4ecab803b37c3ee981 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/112181 Commit-Queue: Ryan Macnak <rmacnak@google.com> Reviewed-by: Ben Konyi <bkonyi@google.com>
747 lines
25 KiB
C++
747 lines
25 KiB
C++
// Copyright (c) 2013, 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.
|
|
|
|
#include <memory>
|
|
|
|
#include "platform/globals.h"
|
|
|
|
#include "include/dart_tools_api.h"
|
|
#include "vm/dart_api_impl.h"
|
|
#include "vm/dart_entry.h"
|
|
#include "vm/debugger.h"
|
|
#include "vm/debugger_api_impl_test.h"
|
|
#include "vm/globals.h"
|
|
#include "vm/heap/safepoint.h"
|
|
#include "vm/message_handler.h"
|
|
#include "vm/object_id_ring.h"
|
|
#include "vm/os.h"
|
|
#include "vm/port.h"
|
|
#include "vm/profiler.h"
|
|
#include "vm/service.h"
|
|
#include "vm/unit_test.h"
|
|
|
|
namespace dart {
|
|
|
|
// This flag is used in the Service_Flags test below.
|
|
DEFINE_FLAG(bool, service_testing_flag, false, "Comment");
|
|
|
|
#ifndef PRODUCT
|
|
|
|
class ServiceTestMessageHandler : public MessageHandler {
|
|
public:
|
|
ServiceTestMessageHandler() : _msg(NULL) {}
|
|
|
|
~ServiceTestMessageHandler() {
|
|
PortMap::ClosePorts(this);
|
|
free(_msg);
|
|
}
|
|
|
|
MessageStatus HandleMessage(std::unique_ptr<Message> message) {
|
|
if (_msg != NULL) {
|
|
free(_msg);
|
|
_msg = NULL;
|
|
}
|
|
|
|
// Parse the message.
|
|
Object& response_obj = Object::Handle();
|
|
if (message->IsRaw()) {
|
|
response_obj = message->raw_obj();
|
|
} else {
|
|
Thread* thread = Thread::Current();
|
|
MessageSnapshotReader reader(message.get(), thread);
|
|
response_obj = reader.ReadObject();
|
|
}
|
|
if (response_obj.IsString()) {
|
|
String& response = String::Handle();
|
|
response ^= response_obj.raw();
|
|
_msg = strdup(response.ToCString());
|
|
} else {
|
|
ASSERT(response_obj.IsArray());
|
|
Array& response_array = Array::Handle();
|
|
response_array ^= response_obj.raw();
|
|
ASSERT(response_array.Length() == 1);
|
|
ExternalTypedData& response = ExternalTypedData::Handle();
|
|
response ^= response_array.At(0);
|
|
_msg = strdup(reinterpret_cast<char*>(response.DataAddr(0)));
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
const char* msg() const { return _msg; }
|
|
|
|
virtual Isolate* isolate() const { return Isolate::Current(); }
|
|
|
|
private:
|
|
char* _msg;
|
|
};
|
|
|
|
static RawArray* Eval(Dart_Handle lib, const char* expr) {
|
|
const String& dummy_isolate_id = String::Handle(String::New("isolateId"));
|
|
Dart_Handle expr_val;
|
|
{
|
|
TransitionVMToNative transiton(Thread::Current());
|
|
expr_val = Dart_EvaluateStaticExpr(lib, NewString(expr));
|
|
EXPECT_VALID(expr_val);
|
|
}
|
|
Zone* zone = Thread::Current()->zone();
|
|
const GrowableObjectArray& value =
|
|
Api::UnwrapGrowableObjectArrayHandle(zone, expr_val);
|
|
const Array& result = Array::Handle(Array::MakeFixedLength(value));
|
|
GrowableObjectArray& growable = GrowableObjectArray::Handle();
|
|
growable ^= result.At(4);
|
|
// Append dummy isolate id to parameter values.
|
|
growable.Add(dummy_isolate_id);
|
|
Array& array = Array::Handle(Array::MakeFixedLength(growable));
|
|
result.SetAt(4, array);
|
|
growable ^= result.At(5);
|
|
// Append dummy isolate id to parameter values.
|
|
growable.Add(dummy_isolate_id);
|
|
array = Array::MakeFixedLength(growable);
|
|
result.SetAt(5, array);
|
|
return result.raw();
|
|
}
|
|
|
|
static RawArray* EvalF(Dart_Handle lib, const char* fmt, ...) {
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
intptr_t len = Utils::VSNPrint(NULL, 0, fmt, args);
|
|
va_end(args);
|
|
|
|
char* buffer = Thread::Current()->zone()->Alloc<char>(len + 1);
|
|
va_list args2;
|
|
va_start(args2, fmt);
|
|
Utils::VSNPrint(buffer, (len + 1), fmt, args2);
|
|
va_end(args2);
|
|
|
|
return Eval(lib, buffer);
|
|
}
|
|
|
|
static RawFunction* GetFunction(const Class& cls, const char* name) {
|
|
const Function& result = Function::Handle(
|
|
cls.LookupDynamicFunction(String::Handle(String::New(name))));
|
|
EXPECT(!result.IsNull());
|
|
return result.raw();
|
|
}
|
|
|
|
static RawClass* GetClass(const Library& lib, const char* name) {
|
|
const Class& cls = Class::Handle(
|
|
lib.LookupClass(String::Handle(Symbols::New(Thread::Current(), name))));
|
|
EXPECT(!cls.IsNull()); // No ambiguity error expected.
|
|
return cls.raw();
|
|
}
|
|
|
|
static void HandleIsolateMessage(Isolate* isolate, const Array& msg) {
|
|
Service::HandleIsolateMessage(isolate, msg);
|
|
}
|
|
|
|
static void HandleRootMessage(const Array& message) {
|
|
Service::HandleRootMessage(message);
|
|
}
|
|
|
|
ISOLATE_UNIT_TEST_CASE(Service_IsolateStickyError) {
|
|
const char* kScript = "main() => throw 'HI THERE STICKY';\n";
|
|
|
|
Isolate* isolate = thread->isolate();
|
|
isolate->set_is_runnable(true);
|
|
Dart_Handle result;
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
Dart_Handle lib = TestCase::LoadTestScript(kScript, NULL);
|
|
EXPECT_VALID(lib);
|
|
result = Dart_Invoke(lib, NewString("main"), 0, NULL);
|
|
EXPECT(Dart_IsUnhandledExceptionError(result));
|
|
EXPECT(!Dart_HasStickyError());
|
|
}
|
|
EXPECT(Thread::Current()->sticky_error() == Error::null());
|
|
|
|
{
|
|
JSONStream js;
|
|
isolate->PrintJSON(&js, false);
|
|
// No error property and no PauseExit state.
|
|
EXPECT_NOTSUBSTRING("\"error\":", js.ToCString());
|
|
EXPECT_NOTSUBSTRING("HI THERE STICKY", js.ToCString());
|
|
EXPECT_NOTSUBSTRING("PauseExit", js.ToCString());
|
|
}
|
|
|
|
{
|
|
// Set the sticky error.
|
|
TransitionVMToNative transition(thread);
|
|
Dart_SetStickyError(result);
|
|
Dart_SetPausedOnExit(true);
|
|
EXPECT(Dart_HasStickyError());
|
|
}
|
|
|
|
{
|
|
JSONStream js;
|
|
isolate->PrintJSON(&js, false);
|
|
// Error and PauseExit set.
|
|
EXPECT_SUBSTRING("\"error\":", js.ToCString());
|
|
EXPECT_SUBSTRING("HI THERE STICKY", js.ToCString());
|
|
EXPECT_SUBSTRING("PauseExit", js.ToCString());
|
|
}
|
|
}
|
|
|
|
ISOLATE_UNIT_TEST_CASE(Service_IdZones) {
|
|
Zone* zone = thread->zone();
|
|
Isolate* isolate = thread->isolate();
|
|
ObjectIdRing* ring = isolate->object_id_ring();
|
|
|
|
const String& test_a = String::Handle(zone, String::New("a"));
|
|
const String& test_b = String::Handle(zone, String::New("b"));
|
|
const String& test_c = String::Handle(zone, String::New("c"));
|
|
const String& test_d = String::Handle(zone, String::New("d"));
|
|
|
|
// Both RingServiceIdZones share the same backing store and id space.
|
|
|
|
// Always allocate a new id.
|
|
RingServiceIdZone always_new_zone;
|
|
always_new_zone.Init(ring, ObjectIdRing::kAllocateId);
|
|
EXPECT_STREQ("objects/0", always_new_zone.GetServiceId(test_a));
|
|
EXPECT_STREQ("objects/1", always_new_zone.GetServiceId(test_a));
|
|
EXPECT_STREQ("objects/2", always_new_zone.GetServiceId(test_a));
|
|
EXPECT_STREQ("objects/3", always_new_zone.GetServiceId(test_b));
|
|
EXPECT_STREQ("objects/4", always_new_zone.GetServiceId(test_c));
|
|
|
|
// Reuse an existing id or allocate a new id.
|
|
RingServiceIdZone reuse_zone;
|
|
reuse_zone.Init(ring, ObjectIdRing::kReuseId);
|
|
EXPECT_STREQ("objects/0", reuse_zone.GetServiceId(test_a));
|
|
EXPECT_STREQ("objects/0", reuse_zone.GetServiceId(test_a));
|
|
EXPECT_STREQ("objects/3", reuse_zone.GetServiceId(test_b));
|
|
EXPECT_STREQ("objects/3", reuse_zone.GetServiceId(test_b));
|
|
EXPECT_STREQ("objects/4", reuse_zone.GetServiceId(test_c));
|
|
EXPECT_STREQ("objects/4", reuse_zone.GetServiceId(test_c));
|
|
EXPECT_STREQ("objects/5", reuse_zone.GetServiceId(test_d));
|
|
EXPECT_STREQ("objects/5", reuse_zone.GetServiceId(test_d));
|
|
}
|
|
|
|
ISOLATE_UNIT_TEST_CASE(Service_Code) {
|
|
const char* kScript =
|
|
"var port;\n" // Set to our mock port by C++.
|
|
"\n"
|
|
"class A {\n"
|
|
" var a;\n"
|
|
" dynamic b() {}\n"
|
|
" dynamic c() {\n"
|
|
" var d = () { b(); };\n"
|
|
" return d;\n"
|
|
" }\n"
|
|
"}\n"
|
|
"main() {\n"
|
|
" var z = new A();\n"
|
|
" var x = z.c();\n"
|
|
" x();\n"
|
|
"}";
|
|
|
|
Isolate* isolate = thread->isolate();
|
|
isolate->set_is_runnable(true);
|
|
Dart_Handle lib;
|
|
Library& vmlib = Library::Handle();
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
lib = TestCase::LoadTestScript(kScript, NULL);
|
|
EXPECT_VALID(lib);
|
|
EXPECT(!Dart_IsNull(lib));
|
|
Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL);
|
|
EXPECT_VALID(result);
|
|
}
|
|
vmlib ^= Api::UnwrapHandle(lib);
|
|
EXPECT(!vmlib.IsNull());
|
|
const Class& class_a = Class::Handle(GetClass(vmlib, "A"));
|
|
EXPECT(!class_a.IsNull());
|
|
const Function& function_c = Function::Handle(GetFunction(class_a, "c"));
|
|
EXPECT(!function_c.IsNull());
|
|
const Code& code_c = Code::Handle(function_c.CurrentCode());
|
|
EXPECT(!code_c.IsNull());
|
|
// Use the entry of the code object as it's reference.
|
|
uword entry = code_c.PayloadStart();
|
|
int64_t compile_timestamp = code_c.compile_timestamp();
|
|
EXPECT_GT(code_c.Size(), 16);
|
|
uword last = entry + code_c.Size();
|
|
|
|
// Build a mock message handler and wrap it in a dart port.
|
|
ServiceTestMessageHandler handler;
|
|
Dart_Port port_id = PortMap::CreatePort(&handler);
|
|
Dart_Handle port = Api::NewHandle(thread, SendPort::New(port_id));
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
EXPECT_VALID(port);
|
|
EXPECT_VALID(Dart_SetField(lib, NewString("port"), port));
|
|
}
|
|
|
|
Array& service_msg = Array::Handle();
|
|
|
|
// Request an invalid code object.
|
|
service_msg =
|
|
Eval(lib, "[0, port, '0', 'getObject', ['objectId'], ['code/0']]");
|
|
HandleIsolateMessage(isolate, service_msg);
|
|
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
|
EXPECT_SUBSTRING("\"error\"", handler.msg());
|
|
|
|
// The following test checks that a code object can be found only
|
|
// at compile_timestamp()-code.EntryPoint().
|
|
service_msg = EvalF(lib,
|
|
"[0, port, '0', 'getObject', "
|
|
"['objectId'], ['code/%" Px64 "-%" Px "']]",
|
|
compile_timestamp, entry);
|
|
HandleIsolateMessage(isolate, service_msg);
|
|
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
|
EXPECT_SUBSTRING("\"type\":\"Code\"", handler.msg());
|
|
{
|
|
// Only perform a partial match.
|
|
const intptr_t kBufferSize = 512;
|
|
char buffer[kBufferSize];
|
|
Utils::SNPrint(buffer, kBufferSize - 1,
|
|
"\"fixedId\":true,\"id\":\"code\\/%" Px64 "-%" Px "\",",
|
|
compile_timestamp, entry);
|
|
EXPECT_SUBSTRING(buffer, handler.msg());
|
|
}
|
|
|
|
// Request code object at compile_timestamp-code.EntryPoint() + 16
|
|
// Expect this to fail because the address is not the entry point.
|
|
uintptr_t address = entry + 16;
|
|
service_msg = EvalF(lib,
|
|
"[0, port, '0', 'getObject', "
|
|
"['objectId'], ['code/%" Px64 "-%" Px "']]",
|
|
compile_timestamp, address);
|
|
HandleIsolateMessage(isolate, service_msg);
|
|
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
|
EXPECT_SUBSTRING("\"error\"", handler.msg());
|
|
|
|
// Request code object at (compile_timestamp - 1)-code.EntryPoint()
|
|
// Expect this to fail because the timestamp is wrong.
|
|
address = entry;
|
|
service_msg = EvalF(lib,
|
|
"[0, port, '0', 'getObject', "
|
|
"['objectId'], ['code/%" Px64 "-%" Px "']]",
|
|
compile_timestamp - 1, address);
|
|
HandleIsolateMessage(isolate, service_msg);
|
|
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
|
EXPECT_SUBSTRING("\"error\"", handler.msg());
|
|
|
|
// Request native code at address. Expect the null code object back.
|
|
address = last;
|
|
service_msg = EvalF(lib,
|
|
"[0, port, '0', 'getObject', "
|
|
"['objectId'], ['code/native-%" Px "']]",
|
|
address);
|
|
HandleIsolateMessage(isolate, service_msg);
|
|
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
|
// TODO(turnidge): It is pretty broken to return an Instance here. Fix.
|
|
EXPECT_SUBSTRING("\"kind\":\"Null\"", handler.msg());
|
|
|
|
// Request malformed native code.
|
|
service_msg = EvalF(lib,
|
|
"[0, port, '0', 'getObject', ['objectId'], "
|
|
"['code/native%" Px "']]",
|
|
address);
|
|
HandleIsolateMessage(isolate, service_msg);
|
|
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
|
EXPECT_SUBSTRING("\"error\"", handler.msg());
|
|
}
|
|
|
|
ISOLATE_UNIT_TEST_CASE(Service_PcDescriptors) {
|
|
const char* kScript =
|
|
"var port;\n" // Set to our mock port by C++.
|
|
"\n"
|
|
"class A {\n"
|
|
" var a;\n"
|
|
" dynamic b() {}\n"
|
|
" dynamic c() {\n"
|
|
" var d = () { b(); };\n"
|
|
" return d;\n"
|
|
" }\n"
|
|
"}\n"
|
|
"main() {\n"
|
|
" var z = new A();\n"
|
|
" var x = z.c();\n"
|
|
" x();\n"
|
|
"}";
|
|
|
|
Isolate* isolate = thread->isolate();
|
|
isolate->set_is_runnable(true);
|
|
Dart_Handle lib;
|
|
Library& vmlib = Library::Handle();
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
lib = TestCase::LoadTestScript(kScript, NULL);
|
|
EXPECT_VALID(lib);
|
|
EXPECT(!Dart_IsNull(lib));
|
|
Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL);
|
|
EXPECT_VALID(result);
|
|
}
|
|
vmlib ^= Api::UnwrapHandle(lib);
|
|
EXPECT(!vmlib.IsNull());
|
|
const Class& class_a = Class::Handle(GetClass(vmlib, "A"));
|
|
EXPECT(!class_a.IsNull());
|
|
const Function& function_c = Function::Handle(GetFunction(class_a, "c"));
|
|
EXPECT(!function_c.IsNull());
|
|
const Code& code_c = Code::Handle(function_c.CurrentCode());
|
|
EXPECT(!code_c.IsNull());
|
|
|
|
const PcDescriptors& descriptors =
|
|
PcDescriptors::Handle(code_c.pc_descriptors());
|
|
EXPECT(!descriptors.IsNull());
|
|
ObjectIdRing* ring = isolate->object_id_ring();
|
|
intptr_t id = ring->GetIdForObject(descriptors.raw());
|
|
|
|
// Build a mock message handler and wrap it in a dart port.
|
|
ServiceTestMessageHandler handler;
|
|
Dart_Port port_id = PortMap::CreatePort(&handler);
|
|
Dart_Handle port = Api::NewHandle(thread, SendPort::New(port_id));
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
EXPECT_VALID(port);
|
|
EXPECT_VALID(Dart_SetField(lib, NewString("port"), port));
|
|
}
|
|
|
|
Array& service_msg = Array::Handle();
|
|
|
|
// Fetch object.
|
|
service_msg = EvalF(lib,
|
|
"[0, port, '0', 'getObject', "
|
|
"['objectId'], ['objects/%" Pd "']]",
|
|
id);
|
|
HandleIsolateMessage(isolate, service_msg);
|
|
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
|
// Check type.
|
|
EXPECT_SUBSTRING("\"type\":\"Object\"", handler.msg());
|
|
EXPECT_SUBSTRING("\"_vmType\":\"PcDescriptors\"", handler.msg());
|
|
// Check for members array.
|
|
EXPECT_SUBSTRING("\"members\":[", handler.msg());
|
|
}
|
|
|
|
ISOLATE_UNIT_TEST_CASE(Service_LocalVarDescriptors) {
|
|
const char* kScript =
|
|
"var port;\n" // Set to our mock port by C++.
|
|
"\n"
|
|
"class A {\n"
|
|
" var a;\n"
|
|
" dynamic b() {}\n"
|
|
" dynamic c() {\n"
|
|
" var d = () { b(); };\n"
|
|
" return d;\n"
|
|
" }\n"
|
|
"}\n"
|
|
"main() {\n"
|
|
" var z = new A();\n"
|
|
" var x = z.c();\n"
|
|
" x();\n"
|
|
"}";
|
|
|
|
Isolate* isolate = thread->isolate();
|
|
isolate->set_is_runnable(true);
|
|
Dart_Handle lib;
|
|
Library& vmlib = Library::Handle();
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
lib = TestCase::LoadTestScript(kScript, NULL);
|
|
EXPECT_VALID(lib);
|
|
EXPECT(!Dart_IsNull(lib));
|
|
Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL);
|
|
EXPECT_VALID(result);
|
|
}
|
|
vmlib ^= Api::UnwrapHandle(lib);
|
|
EXPECT(!vmlib.IsNull());
|
|
const Class& class_a = Class::Handle(GetClass(vmlib, "A"));
|
|
EXPECT(!class_a.IsNull());
|
|
const Function& function_c = Function::Handle(GetFunction(class_a, "c"));
|
|
EXPECT(!function_c.IsNull());
|
|
const Code& code_c = Code::Handle(function_c.CurrentCode());
|
|
EXPECT(!code_c.IsNull());
|
|
|
|
const LocalVarDescriptors& descriptors =
|
|
LocalVarDescriptors::Handle(code_c.GetLocalVarDescriptors());
|
|
// Generate an ID for this object.
|
|
ObjectIdRing* ring = isolate->object_id_ring();
|
|
intptr_t id = ring->GetIdForObject(descriptors.raw());
|
|
|
|
// Build a mock message handler and wrap it in a dart port.
|
|
ServiceTestMessageHandler handler;
|
|
Dart_Port port_id = PortMap::CreatePort(&handler);
|
|
Dart_Handle port = Api::NewHandle(thread, SendPort::New(port_id));
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
EXPECT_VALID(port);
|
|
EXPECT_VALID(Dart_SetField(lib, NewString("port"), port));
|
|
}
|
|
|
|
Array& service_msg = Array::Handle();
|
|
|
|
// Fetch object.
|
|
service_msg = EvalF(lib,
|
|
"[0, port, '0', 'getObject', "
|
|
"['objectId'], ['objects/%" Pd "']]",
|
|
id);
|
|
HandleIsolateMessage(isolate, service_msg);
|
|
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
|
// Check type.
|
|
EXPECT_SUBSTRING("\"type\":\"Object\"", handler.msg());
|
|
EXPECT_SUBSTRING("\"_vmType\":\"LocalVarDescriptors\"", handler.msg());
|
|
// Check for members array.
|
|
EXPECT_SUBSTRING("\"members\":[", handler.msg());
|
|
}
|
|
|
|
static void WeakHandleFinalizer(void* isolate_callback_data,
|
|
Dart_WeakPersistentHandle handle,
|
|
void* peer) {}
|
|
|
|
ISOLATE_UNIT_TEST_CASE(Service_PersistentHandles) {
|
|
const char* kScript =
|
|
"var port;\n" // Set to our mock port by C++.
|
|
"\n"
|
|
"class A {\n"
|
|
" var a;\n"
|
|
"}\n"
|
|
"var global = new A();\n"
|
|
"main() {\n"
|
|
" return global;\n"
|
|
"}";
|
|
|
|
Isolate* isolate = thread->isolate();
|
|
isolate->set_is_runnable(true);
|
|
|
|
Dart_Handle lib;
|
|
Dart_PersistentHandle persistent_handle;
|
|
Dart_WeakPersistentHandle weak_persistent_handle;
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
lib = TestCase::LoadTestScript(kScript, NULL);
|
|
EXPECT_VALID(lib);
|
|
Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL);
|
|
EXPECT_VALID(result);
|
|
|
|
// Create a persistent handle to global.
|
|
persistent_handle = Dart_NewPersistentHandle(result);
|
|
|
|
// Create a weak persistent handle to global.
|
|
weak_persistent_handle = Dart_NewWeakPersistentHandle(
|
|
result, reinterpret_cast<void*>(0xdeadbeef), 128, WeakHandleFinalizer);
|
|
}
|
|
|
|
// Build a mock message handler and wrap it in a dart port.
|
|
ServiceTestMessageHandler handler;
|
|
Dart_Port port_id = PortMap::CreatePort(&handler);
|
|
Dart_Handle port = Api::NewHandle(thread, SendPort::New(port_id));
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
EXPECT_VALID(port);
|
|
EXPECT_VALID(Dart_SetField(lib, NewString("port"), port));
|
|
}
|
|
|
|
Array& service_msg = Array::Handle();
|
|
|
|
// Get persistent handles.
|
|
service_msg = Eval(lib, "[0, port, '0', '_getPersistentHandles', [], []]");
|
|
HandleIsolateMessage(isolate, service_msg);
|
|
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
|
// Look for a heart beat.
|
|
EXPECT_SUBSTRING("\"type\":\"_PersistentHandles\"", handler.msg());
|
|
EXPECT_SUBSTRING("\"peer\":\"0xdeadbeef\"", handler.msg());
|
|
EXPECT_SUBSTRING("\"name\":\"A\"", handler.msg());
|
|
EXPECT_SUBSTRING("\"externalSize\":\"128\"", handler.msg());
|
|
|
|
// Delete persistent handles.
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
Dart_DeletePersistentHandle(persistent_handle);
|
|
Dart_DeleteWeakPersistentHandle(Dart_CurrentIsolate(),
|
|
weak_persistent_handle);
|
|
}
|
|
|
|
// Get persistent handles (again).
|
|
service_msg = Eval(lib, "[0, port, '0', '_getPersistentHandles', [], []]");
|
|
HandleIsolateMessage(isolate, service_msg);
|
|
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
|
EXPECT_SUBSTRING("\"type\":\"_PersistentHandles\"", handler.msg());
|
|
// Verify that old persistent handles are not present.
|
|
EXPECT_NOTSUBSTRING("\"peer\":\"0xdeadbeef\"", handler.msg());
|
|
EXPECT_NOTSUBSTRING("\"name\":\"A\"", handler.msg());
|
|
EXPECT_NOTSUBSTRING("\"externalSize\":\"128\"", handler.msg());
|
|
}
|
|
|
|
static bool alpha_callback(const char* name,
|
|
const char** option_keys,
|
|
const char** option_values,
|
|
intptr_t num_options,
|
|
void* user_data,
|
|
const char** result) {
|
|
*result = strdup("alpha");
|
|
return true;
|
|
}
|
|
|
|
static bool beta_callback(const char* name,
|
|
const char** option_keys,
|
|
const char** option_values,
|
|
intptr_t num_options,
|
|
void* user_data,
|
|
const char** result) {
|
|
*result = strdup("beta");
|
|
return false;
|
|
}
|
|
|
|
ISOLATE_UNIT_TEST_CASE(Service_EmbedderRootHandler) {
|
|
const char* kScript =
|
|
"var port;\n" // Set to our mock port by C++.
|
|
"\n"
|
|
"var x = 7;\n"
|
|
"main() {\n"
|
|
" x = x * x;\n"
|
|
" x = (x / 13).floor();\n"
|
|
"}";
|
|
|
|
Dart_Handle lib;
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
|
|
Dart_RegisterRootServiceRequestCallback("alpha", alpha_callback, NULL);
|
|
Dart_RegisterRootServiceRequestCallback("beta", beta_callback, NULL);
|
|
|
|
lib = TestCase::LoadTestScript(kScript, NULL);
|
|
EXPECT_VALID(lib);
|
|
Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL);
|
|
EXPECT_VALID(result);
|
|
}
|
|
|
|
// Build a mock message handler and wrap it in a dart port.
|
|
ServiceTestMessageHandler handler;
|
|
Dart_Port port_id = PortMap::CreatePort(&handler);
|
|
Dart_Handle port = Api::NewHandle(thread, SendPort::New(port_id));
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
EXPECT_VALID(port);
|
|
EXPECT_VALID(Dart_SetField(lib, NewString("port"), port));
|
|
}
|
|
|
|
Array& service_msg = Array::Handle();
|
|
service_msg = Eval(lib, "[0, port, '\"', 'alpha', [], []]");
|
|
HandleRootMessage(service_msg);
|
|
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
|
EXPECT_STREQ("{\"jsonrpc\":\"2.0\", \"result\":alpha,\"id\":\"\\\"\"}",
|
|
handler.msg());
|
|
service_msg = Eval(lib, "[0, port, 1, 'beta', [], []]");
|
|
HandleRootMessage(service_msg);
|
|
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
|
EXPECT_STREQ("{\"jsonrpc\":\"2.0\", \"error\":beta,\"id\":1}", handler.msg());
|
|
}
|
|
|
|
ISOLATE_UNIT_TEST_CASE(Service_EmbedderIsolateHandler) {
|
|
const char* kScript =
|
|
"var port;\n" // Set to our mock port by C++.
|
|
"\n"
|
|
"var x = 7;\n"
|
|
"main() {\n"
|
|
" x = x * x;\n"
|
|
" x = (x / 13).floor();\n"
|
|
"}";
|
|
|
|
Dart_Handle lib;
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
|
|
Dart_RegisterIsolateServiceRequestCallback("alpha", alpha_callback, NULL);
|
|
Dart_RegisterIsolateServiceRequestCallback("beta", beta_callback, NULL);
|
|
|
|
lib = TestCase::LoadTestScript(kScript, NULL);
|
|
EXPECT_VALID(lib);
|
|
Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL);
|
|
EXPECT_VALID(result);
|
|
}
|
|
|
|
// Build a mock message handler and wrap it in a dart port.
|
|
ServiceTestMessageHandler handler;
|
|
Dart_Port port_id = PortMap::CreatePort(&handler);
|
|
Dart_Handle port = Api::NewHandle(thread, SendPort::New(port_id));
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
EXPECT_VALID(port);
|
|
EXPECT_VALID(Dart_SetField(lib, NewString("port"), port));
|
|
}
|
|
|
|
Isolate* isolate = thread->isolate();
|
|
Array& service_msg = Array::Handle();
|
|
service_msg = Eval(lib, "[0, port, '0', 'alpha', [], []]");
|
|
HandleIsolateMessage(isolate, service_msg);
|
|
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
|
EXPECT_STREQ("{\"jsonrpc\":\"2.0\", \"result\":alpha,\"id\":\"0\"}",
|
|
handler.msg());
|
|
service_msg = Eval(lib, "[0, port, '0', 'beta', [], []]");
|
|
HandleIsolateMessage(isolate, service_msg);
|
|
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
|
EXPECT_STREQ("{\"jsonrpc\":\"2.0\", \"error\":beta,\"id\":\"0\"}",
|
|
handler.msg());
|
|
}
|
|
|
|
// TODO(zra): Remove when tests are ready to enable.
|
|
#if !defined(TARGET_ARCH_ARM64)
|
|
|
|
static void EnableProfiler() {
|
|
if (!FLAG_profiler) {
|
|
FLAG_profiler = true;
|
|
Profiler::Init();
|
|
}
|
|
}
|
|
|
|
ISOLATE_UNIT_TEST_CASE(Service_Profile) {
|
|
EnableProfiler();
|
|
const char* kScript =
|
|
"var port;\n" // Set to our mock port by C++.
|
|
"\n"
|
|
"var x = 7;\n"
|
|
"main() {\n"
|
|
" x = x * x;\n"
|
|
" x = (x / 13).floor();\n"
|
|
"}";
|
|
|
|
Isolate* isolate = thread->isolate();
|
|
isolate->set_is_runnable(true);
|
|
Dart_Handle lib;
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
|
|
lib = TestCase::LoadTestScript(kScript, NULL);
|
|
EXPECT_VALID(lib);
|
|
Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL);
|
|
EXPECT_VALID(result);
|
|
}
|
|
|
|
// Build a mock message handler and wrap it in a dart port.
|
|
ServiceTestMessageHandler handler;
|
|
Dart_Port port_id = PortMap::CreatePort(&handler);
|
|
Dart_Handle port = Api::NewHandle(thread, SendPort::New(port_id));
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
EXPECT_VALID(port);
|
|
EXPECT_VALID(Dart_SetField(lib, NewString("port"), port));
|
|
}
|
|
|
|
Array& service_msg = Array::Handle();
|
|
service_msg = Eval(lib, "[0, port, '0', '_getCpuProfile', [], []]");
|
|
HandleIsolateMessage(isolate, service_msg);
|
|
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
|
// Expect error (tags required).
|
|
EXPECT_SUBSTRING("\"error\"", handler.msg());
|
|
|
|
service_msg =
|
|
Eval(lib, "[0, port, '0', '_getCpuProfile', ['tags'], ['None']]");
|
|
HandleIsolateMessage(isolate, service_msg);
|
|
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
|
// Expect profile
|
|
EXPECT_SUBSTRING("\"type\":\"_CpuProfile\"", handler.msg());
|
|
|
|
service_msg =
|
|
Eval(lib, "[0, port, '0', '_getCpuProfile', ['tags'], ['Bogus']]");
|
|
HandleIsolateMessage(isolate, service_msg);
|
|
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
|
// Expect error.
|
|
EXPECT_SUBSTRING("\"error\"", handler.msg());
|
|
}
|
|
|
|
#endif // !defined(TARGET_ARCH_ARM64)
|
|
|
|
#endif // !PRODUCT
|
|
|
|
} // namespace dart
|