blob: a247e2d5a5a31b43945aacdddd22968ed8632696 [file] [log] [blame] [edit]
// 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 "vm/service.h"
#include <cstdint>
#include <memory>
#include <utility>
#include "include/dart_api.h"
#include "include/dart_native_api.h"
#include "platform/globals.h"
#include "platform/unicode.h"
#include "platform/utils.h"
#include "vm/base64.h"
#include "vm/canonical_tables.h"
#include "vm/closure_functions_cache.h"
#include "vm/compiler/jit/compiler.h"
#include "vm/cpu.h"
#include "vm/dart_api_impl.h"
#include "vm/dart_api_message.h"
#include "vm/dart_api_state.h"
#include "vm/dart_entry.h"
#include "vm/debugger.h"
#include "vm/heap/safepoint.h"
#include "vm/isolate.h"
#include "vm/json_stream.h"
#include "vm/kernel_isolate.h"
#include "vm/lockers.h"
#include "vm/message.h"
#include "vm/message_handler.h"
#include "vm/message_snapshot.h"
#include "vm/native_arguments.h"
#include "vm/native_entry.h"
#include "vm/native_symbol.h"
#include "vm/object.h"
#include "vm/object_graph.h"
#include "vm/object_id_ring.h"
#include "vm/object_store.h"
#include "vm/parser.h"
#include "vm/port.h"
#include "vm/profiler.h"
#include "vm/profiler_service.h"
#include "vm/raw_object_fields.h"
#include "vm/resolver.h"
#include "vm/reusable_handles.h"
#include "vm/service_event.h"
#include "vm/service_isolate.h"
#include "vm/source_report.h"
#include "vm/stack_frame.h"
#include "vm/symbols.h"
#include "vm/timeline.h"
#include "vm/version.h"
#include "vm/visitor.h"
#if defined(SUPPORT_PERFETTO)
#include "vm/perfetto_utils.h"
#endif // defined(SUPPORT_PERFETTO)
namespace dart {
#define Z (T->zone())
DECLARE_FLAG(bool, trace_service);
DECLARE_FLAG(bool, trace_service_pause_events);
DECLARE_FLAG(bool, profile_vm);
DEFINE_FLAG(charp,
vm_name,
"vm",
"The default name of this vm as reported by the VM service "
"protocol");
DEFINE_FLAG(bool,
warn_on_pause_with_no_debugger,
false,
"Print a message when an isolate is paused but there is no "
"debugger attached.");
DEFINE_FLAG(
charp,
log_service_response_sizes,
nullptr,
"Log sizes of service responses and events to a file in CSV format.");
void* Service::service_response_size_log_file_ = nullptr;
void Service::LogResponseSize(const char* method, JSONStream* js) {
if (service_response_size_log_file_ == nullptr) {
return;
}
Dart_FileWriteCallback file_write = Dart::file_write_callback();
char* entry =
OS::SCreate(nullptr, "%s, %" Pd "\n", method, js->buffer()->length());
(*file_write)(entry, strlen(entry), service_response_size_log_file_);
free(entry);
}
void Service::Init() {
if (FLAG_log_service_response_sizes == nullptr) {
return;
}
Dart_FileOpenCallback file_open = Dart::file_open_callback();
Dart_FileWriteCallback file_write = Dart::file_write_callback();
Dart_FileCloseCallback file_close = Dart::file_close_callback();
if ((file_open == nullptr) || (file_write == nullptr) ||
(file_close == nullptr)) {
OS::PrintErr("Error: Could not access file callbacks.");
UNREACHABLE();
}
ASSERT(service_response_size_log_file_ == nullptr);
service_response_size_log_file_ =
(*file_open)(FLAG_log_service_response_sizes, true);
if (service_response_size_log_file_ == nullptr) {
OS::PrintErr("Warning: Failed to open service response size log file: %s\n",
FLAG_log_service_response_sizes);
return;
}
}
void Service::Cleanup() {
if (service_response_size_log_file_ == nullptr) {
return;
}
Dart_FileCloseCallback file_close = Dart::file_close_callback();
(*file_close)(service_response_size_log_file_);
service_response_size_log_file_ = nullptr;
}
static void PrintInvalidParamError(JSONStream* js, const char* param) {
#if !defined(PRODUCT)
js->PrintError(kInvalidParams, "%s: invalid '%s' parameter: %s", js->method(),
param, js->LookupParam(param));
#endif
}
// TODO(johnmccutchan): Split into separate file and write unit tests.
class MethodParameter {
public:
MethodParameter(const char* name, bool required)
: name_(name), required_(required) {}
virtual ~MethodParameter() {}
virtual bool Validate(const char* value) const { return true; }
virtual bool ValidateObject(const Object& value) const { return true; }
const char* name() const { return name_; }
bool required() const { return required_; }
virtual void PrintError(const char* name,
const char* value,
JSONStream* js) const {
PrintInvalidParamError(js, name);
}
virtual void PrintErrorObject(const char* name,
const Object& value,
JSONStream* js) const {
PrintInvalidParamError(js, name);
}
private:
const char* name_;
bool required_;
};
class NoSuchParameter : public MethodParameter {
public:
explicit NoSuchParameter(const char* name) : MethodParameter(name, false) {}
virtual bool Validate(const char* value) const { return (value == nullptr); }
virtual bool ValidateObject(const Object& value) const {
return value.IsNull();
}
};
#define ISOLATE_PARAMETER new IdParameter("isolateId", true)
#define ISOLATE_GROUP_PARAMETER new IdParameter("isolateGroupId", true)
#define NO_ISOLATE_PARAMETER new NoSuchParameter("isolateId")
#define RUNNABLE_ISOLATE_PARAMETER new RunnableIsolateParameter("isolateId")
#define OBJECT_PARAMETER new IdParameter("objectId", true)
static bool ValidateUIntParameter(const char* value) {
if (value == nullptr) {
return false;
}
for (const char* cp = value; *cp != '\0'; cp++) {
if (*cp < '0' || *cp > '9') {
return false;
}
}
return true;
}
class UIntParameter : public MethodParameter {
public:
UIntParameter(const char* name, bool required)
: MethodParameter(name, required) {}
virtual bool Validate(const char* value) const {
return ValidateUIntParameter(value);
}
static uintptr_t Parse(const char* value) {
if (value == nullptr) {
return -1;
}
char* end_ptr = nullptr;
uintptr_t result = strtoul(value, &end_ptr, 10);
ASSERT(*end_ptr == '\0'); // Parsed full string
return result;
}
};
class EnumListParameter : public MethodParameter {
public:
EnumListParameter(const char* name, bool required, const char* const* enums)
: MethodParameter(name, required), enums_(enums) {}
virtual bool Validate(const char* value) const {
return ElementCount(value) >= 0;
}
const char** Parse(char* value) const {
const char* kJsonChars = " \t\r\n[,]";
// Make a writeable copy of the value.
intptr_t element_count = ElementCount(value);
if (element_count < 0) {
return nullptr;
}
intptr_t element_pos = 0;
// Allocate our element array. +1 for nullptr terminator.
// The caller is responsible for deleting this memory.
char** elements = new char*[element_count + 1];
elements[element_count] = nullptr;
// Parse the string destructively. Build the list of elements.
while (element_pos < element_count) {
// Skip to the next element.
value += strspn(value, kJsonChars);
intptr_t len = strcspn(value, kJsonChars);
ASSERT(len > 0); // We rely on the parameter being validated already.
value[len] = '\0';
elements[element_pos++] = value;
// Advance. +1 for null terminator.
value += (len + 1);
}
return const_cast<const char**>(elements);
}
private:
// For now observatory enums are ascii letters plus underscore.
static bool IsEnumChar(char c) {
return (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) ||
(c == '_'));
}
// Returns number of elements in the list. -1 on parse error.
intptr_t ElementCount(const char* value) const {
const char* kJsonWhitespaceChars = " \t\r\n";
if (value == nullptr) {
return -1;
}
const char* cp = value;
cp += strspn(cp, kJsonWhitespaceChars);
if (*cp++ != '[') {
// Missing initial [.
return -1;
}
bool closed = false;
bool element_allowed = true;
intptr_t element_count = 0;
while (true) {
// Skip json whitespace.
cp += strspn(cp, kJsonWhitespaceChars);
switch (*cp) {
case '\0':
return closed ? element_count : -1;
case ']':
closed = true;
cp++;
break;
case ',':
if (element_allowed) {
return -1;
}
element_allowed = true;
cp++;
break;
default:
if (!element_allowed) {
return -1;
}
bool valid_enum = false;
const char* id_start = cp;
while (IsEnumChar(*cp)) {
cp++;
}
if (cp == id_start) {
// Empty identifier, something like this [,].
return -1;
}
intptr_t id_len = cp - id_start;
if (enums_ != nullptr) {
for (intptr_t i = 0; enums_[i] != nullptr; i++) {
intptr_t len = strlen(enums_[i]);
if (len == id_len && strncmp(id_start, enums_[i], len) == 0) {
element_count++;
valid_enum = true;
element_allowed = false; // we need a comma first.
break;
}
}
}
if (!valid_enum) {
return -1;
}
break;
}
}
}
const char* const* enums_;
};
#if defined(SUPPORT_TIMELINE)
static const char* const timeline_streams_enum_names[] = {
"all",
#define DEFINE_NAME(name, ...) #name,
TIMELINE_STREAM_LIST(DEFINE_NAME)
#undef DEFINE_NAME
nullptr};
static const MethodParameter* const set_vm_timeline_flags_params[] = {
NO_ISOLATE_PARAMETER,
new EnumListParameter("recordedStreams",
false,
timeline_streams_enum_names),
nullptr,
};
static bool HasStream(const char** recorded_streams, const char* stream) {
while (*recorded_streams != nullptr) {
if ((strstr(*recorded_streams, "all") != nullptr) ||
(strstr(*recorded_streams, stream) != nullptr)) {
return true;
}
recorded_streams++;
}
return false;
}
bool Service::EnableTimelineStreams(char* categories_list) {
const EnumListParameter* recorded_streams_param =
static_cast<const EnumListParameter*>(set_vm_timeline_flags_params[1]);
const char** streams = recorded_streams_param->Parse(categories_list);
if (streams == nullptr) {
return false;
}
#define SET_ENABLE_STREAM(name, ...) \
Timeline::SetStream##name##Enabled(HasStream(streams, #name));
TIMELINE_STREAM_LIST(SET_ENABLE_STREAM);
#undef SET_ENABLE_STREAM
delete[] streams;
#if !defined(PRODUCT)
// Notify clients that the set of subscribed streams has been updated.
if (Service::timeline_stream.enabled()) {
ServiceEvent event(ServiceEvent::kTimelineStreamSubscriptionsUpdate);
Service::HandleEvent(&event);
}
#endif
return true;
}
#endif // defined(SUPPORT_TIMELINE)
#ifndef PRODUCT
// The name of this of this vm as reported by the VM service protocol.
static char* vm_name = nullptr;
static const char* GetVMName() {
if (vm_name == nullptr) {
return FLAG_vm_name;
}
return vm_name;
}
ServiceIdZone::ServiceIdZone(intptr_t id, ObjectIdRing::IdPolicy policy)
: id_(id), policy_(policy) {}
ServiceIdZone::~ServiceIdZone() {}
intptr_t ServiceIdZone::StringIdToInt(const char* id_string) {
if (Utils::StrStartsWith(id_string, "zones/") &&
ValidateUIntParameter(id_string + 6)) {
return UIntParameter::Parse(id_string + 6);
}
return -1;
}
RingServiceIdZone::RingServiceIdZone(intptr_t id,
ObjectIdRing::IdPolicy policy,
int32_t capacity)
: ServiceIdZone(id, policy), ring_(capacity) {}
RingServiceIdZone::~RingServiceIdZone() {}
int32_t RingServiceIdZone::GetIdForObject(const ObjectPtr obj) {
return ring_.GetIdForObject(obj, policy());
}
ObjectPtr RingServiceIdZone::GetObjectForId(int32_t id,
ObjectIdRing::LookupResult* kind) {
return ring_.GetObjectForId(id, kind);
}
char* RingServiceIdZone::GetServiceId(const Object& obj) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
ASSERT(zone != nullptr);
const intptr_t object_part_of_service_id = GetIdForObject(obj.ptr());
return zone->PrintToString("objects/%" Pd "/%" Pd, object_part_of_service_id,
id());
}
void RingServiceIdZone::Invalidate() {
ring_.Invalidate();
}
void RingServiceIdZone::VisitPointers(ObjectPointerVisitor& visitor) const {
ring_.VisitPointers(&visitor);
}
void RingServiceIdZone::PrintJSON(JSONStream& js) const {
JSONObject jsobj(&js);
jsobj.AddProperty("type", "IdZone");
jsobj.AddPropertyF("id", "zones/%" Pd, id());
jsobj.AddProperty("backingBufferKind", "Ring");
switch (policy()) {
case dart::ObjectIdRing::IdPolicy::kAllocateId:
jsobj.AddProperty("idAssignmentPolicy", "AlwaysAllocate");
break;
case dart::ObjectIdRing::IdPolicy::kReuseId:
jsobj.AddProperty("idAssignmentPolicy", "ReuseExisting");
break;
}
}
// TODO(johnmccutchan): Unify embedder service handler lists and their APIs.
EmbedderServiceHandler* Service::isolate_service_handler_head_ = nullptr;
EmbedderServiceHandler* Service::root_service_handler_head_ = nullptr;
struct ServiceMethodDescriptor;
const ServiceMethodDescriptor* FindMethod(const char* method_name);
// Support for streams defined in embedders.
Dart_ServiceStreamListenCallback Service::stream_listen_callback_ = nullptr;
Dart_ServiceStreamCancelCallback Service::stream_cancel_callback_ = nullptr;
Dart_GetVMServiceAssetsArchive Service::get_service_assets_callback_ = nullptr;
Dart_EmbedderInformationCallback Service::embedder_information_callback_ =
nullptr;
// These are the set of streams known to the core VM.
StreamInfo Service::vm_stream("VM");
StreamInfo Service::isolate_stream("Isolate");
StreamInfo Service::debug_stream("Debug");
StreamInfo Service::gc_stream("GC");
StreamInfo Service::echo_stream("_Echo");
StreamInfo Service::heapsnapshot_stream("HeapSnapshot");
StreamInfo Service::logging_stream("Logging");
StreamInfo Service::extension_stream("Extension");
StreamInfo Service::timeline_stream("Timeline");
StreamInfo Service::profiler_stream("Profiler");
const uint8_t* Service::dart_library_kernel_ = nullptr;
intptr_t Service::dart_library_kernel_len_ = 0;
// Keep streams_ in sync with the protected streams in
// lib/developer/extension.dart
static StreamInfo* const streams_[] = {
&Service::vm_stream, &Service::isolate_stream,
&Service::debug_stream, &Service::gc_stream,
&Service::echo_stream, &Service::heapsnapshot_stream,
&Service::logging_stream, &Service::extension_stream,
&Service::timeline_stream, &Service::profiler_stream,
};
bool Service::ListenStream(const char* stream_id,
bool include_private_members) {
if (FLAG_trace_service) {
OS::PrintErr("vm-service: starting stream '%s'\n", stream_id);
}
intptr_t num_streams = sizeof(streams_) / sizeof(streams_[0]);
for (intptr_t i = 0; i < num_streams; i++) {
if (strcmp(stream_id, streams_[i]->id()) == 0) {
streams_[i]->set_enabled(true);
streams_[i]->set_include_private_members(include_private_members);
return true;
}
}
if (stream_listen_callback_ != nullptr) {
Thread* T = Thread::Current();
TransitionVMToNative transition(T);
return (*stream_listen_callback_)(stream_id);
}
return false;
}
void Service::CancelStream(const char* stream_id) {
if (FLAG_trace_service) {
OS::PrintErr("vm-service: stopping stream '%s'\n", stream_id);
}
intptr_t num_streams = sizeof(streams_) / sizeof(streams_[0]);
for (intptr_t i = 0; i < num_streams; i++) {
if (strcmp(stream_id, streams_[i]->id()) == 0) {
streams_[i]->set_enabled(false);
return;
}
}
if (stream_cancel_callback_ != nullptr) {
Thread* T = Thread::Current();
TransitionVMToNative transition(T);
return (*stream_cancel_callback_)(stream_id);
}
}
ObjectPtr Service::RequestAssets() {
Thread* T = Thread::Current();
Object& object = Object::Handle();
{
Api::Scope api_scope(T);
Dart_Handle handle;
{
TransitionVMToNative transition(T);
if (get_service_assets_callback_ == nullptr) {
return Object::null();
}
handle = get_service_assets_callback_();
if (Dart_IsError(handle)) {
Dart_PropagateError(handle);
}
}
object = Api::UnwrapHandle(handle);
}
if (object.IsNull()) {
return Object::null();
}
if (!object.IsTypedData()) {
const String& error_message = String::Handle(
String::New("An implementation of Dart_GetVMServiceAssetsArchive "
"should return a Uint8Array or null."));
const Error& error = Error::Handle(ApiError::New(error_message));
Exceptions::PropagateError(error);
return Object::null();
}
const TypedData& typed_data = TypedData::Cast(object);
if (typed_data.ElementSizeInBytes() != 1) {
const String& error_message = String::Handle(
String::New("An implementation of Dart_GetVMServiceAssetsArchive "
"should return a Uint8Array or null."));
const Error& error = Error::Handle(ApiError::New(error_message));
Exceptions::PropagateError(error);
return Object::null();
}
return object.ptr();
}
static void PrintSuccess(JSONStream* js) {
JSONObject jsobj(js);
jsobj.AddProperty("type", "Success");
}
static bool CheckDebuggerDisabled(Thread* thread, JSONStream* js) {
#if defined(DART_PRECOMPILED_RUNTIME)
js->PrintError(kFeatureDisabled, "Debugger is disabled in AOT mode.");
return true;
#else
if (thread->isolate()->debugger() == nullptr) {
js->PrintError(kFeatureDisabled, "Debugger is disabled.");
return true;
}
return false;
#endif
}
static bool CheckProfilerDisabled(Thread* thread, JSONStream* js) {
if (!FLAG_profiler) {
js->PrintError(kFeatureDisabled, "Profiler is disabled.");
return true;
}
return false;
}
static bool GetIntegerId(const char* s, intptr_t* id, int base = 10) {
if ((s == nullptr) || (*s == '\0')) {
// Empty string.
return false;
}
if (id == nullptr) {
// No id pointer.
return false;
}
intptr_t r = 0;
char* end_ptr = nullptr;
#if defined(ARCH_IS_32_BIT)
r = strtol(s, &end_ptr, base);
#else
r = strtoll(s, &end_ptr, base);
#endif
if (end_ptr == s) {
// String was not advanced at all, cannot be valid.
return false;
}
*id = r;
return true;
}
static bool GetUnsignedIntegerId(const char* s, uintptr_t* id, int base = 10) {
if ((s == nullptr) || (*s == '\0')) {
// Empty string.
return false;
}
if (id == nullptr) {
// No id pointer.
return false;
}
uintptr_t r = 0;
char* end_ptr = nullptr;
#if defined(ARCH_IS_32_BIT)
r = strtoul(s, &end_ptr, base);
#else
r = strtoull(s, &end_ptr, base);
#endif
if (end_ptr == s) {
// String was not advanced at all, cannot be valid.
return false;
}
*id = r;
return true;
}
static bool GetInteger64Id(const char* s, int64_t* id, int base = 10) {
if ((s == nullptr) || (*s == '\0')) {
// Empty string.
return false;
}
if (id == nullptr) {
// No id pointer.
return false;
}
int64_t r = 0;
char* end_ptr = nullptr;
r = strtoll(s, &end_ptr, base);
if (end_ptr == s) {
// String was not advanced at all, cannot be valid.
return false;
}
*id = r;
return true;
}
// Scans the string until the '-' character. Returns pointer to string
// at '-' character. Returns nullptr if not found.
static const char* ScanUntilDash(const char* s) {
if ((s == nullptr) || (*s == '\0')) {
// Empty string.
return nullptr;
}
while (*s != '\0') {
if (*s == '-') {
return s;
}
s++;
}
return nullptr;
}
static bool GetCodeId(const char* s, int64_t* timestamp, uword* address) {
if ((s == nullptr) || (*s == '\0')) {
// Empty string.
return false;
}
if ((timestamp == nullptr) || (address == nullptr)) {
// Bad arguments.
return false;
}
// Extract the timestamp.
if (!GetInteger64Id(s, timestamp, 16) || (*timestamp < 0)) {
return false;
}
s = ScanUntilDash(s);
if (s == nullptr) {
return false;
}
// Skip the dash.
s++;
// Extract the PC.
if (!GetUnsignedIntegerId(s, address, 16)) {
return false;
}
return true;
}
// Verifies that |s| begins with |prefix| and then calls |GetIntegerId| on
// the remainder of |s|.
static bool GetPrefixedIntegerId(const char* s,
const char* prefix,
intptr_t* service_id) {
if (s == nullptr) {
return false;
}
ASSERT(prefix != nullptr);
const intptr_t kInputLen = strlen(s);
const intptr_t kPrefixLen = strlen(prefix);
ASSERT(kPrefixLen > 0);
if (kInputLen <= kPrefixLen) {
return false;
}
if (strncmp(s, prefix, kPrefixLen) != 0) {
return false;
}
// Prefix satisfied. Move forward.
s += kPrefixLen;
// Attempt to read integer id.
return GetIntegerId(s, service_id);
}
static bool IsValidClassId(Isolate* isolate, intptr_t cid) {
ASSERT(isolate != nullptr);
ClassTable* class_table = isolate->group()->class_table();
ASSERT(class_table != nullptr);
return class_table->IsValidIndex(cid) && class_table->HasValidClassAt(cid);
}
static ClassPtr GetClassForId(Isolate* isolate, intptr_t cid) {
ASSERT(isolate == Isolate::Current());
ASSERT(isolate != nullptr);
ClassTable* class_table = isolate->group()->class_table();
ASSERT(class_table != nullptr);
return class_table->At(cid);
}
class DartStringParameter : public MethodParameter {
public:
DartStringParameter(const char* name, bool required)
: MethodParameter(name, required) {}
virtual bool ValidateObject(const Object& value) const {
return value.IsString();
}
};
class DartListParameter : public MethodParameter {
public:
DartListParameter(const char* name, bool required)
: MethodParameter(name, required) {}
virtual bool ValidateObject(const Object& value) const {
return value.IsArray() || value.IsGrowableObjectArray();
}
};
class BoolParameter : public MethodParameter {
public:
BoolParameter(const char* name, bool required)
: MethodParameter(name, required) {}
virtual bool Validate(const char* value) const {
if (value == nullptr) {
return false;
}
return (strcmp("true", value) == 0) || (strcmp("false", value) == 0);
}
static bool Parse(const char* value, bool default_value = false) {
if (value == nullptr) {
return default_value;
}
return strcmp("true", value) == 0;
}
};
class Int64Parameter : public MethodParameter {
public:
Int64Parameter(const char* name, bool required)
: MethodParameter(name, required) {}
virtual bool Validate(const char* value) const {
if (value == nullptr) {
return false;
}
for (const char* cp = value; *cp != '\0'; cp++) {
if ((*cp < '0' || *cp > '9') && (*cp != '-')) {
return false;
}
}
return true;
}
static int64_t Parse(const char* value, int64_t default_value = -1) {
if ((value == nullptr) || (*value == '\0')) {
return default_value;
}
char* end_ptr = nullptr;
int64_t result = strtoll(value, &end_ptr, 10);
ASSERT(*end_ptr == '\0'); // Parsed full string
return result;
}
};
class UInt64Parameter : public MethodParameter {
public:
UInt64Parameter(const char* name, bool required)
: MethodParameter(name, required) {}
virtual bool Validate(const char* value) const {
if (value == nullptr) {
return false;
}
for (const char* cp = value; *cp != '\0'; cp++) {
if ((*cp < '0' || *cp > '9') && (*cp != '-')) {
return false;
}
}
return true;
}
static uint64_t Parse(const char* value, uint64_t default_value = 0) {
if ((value == nullptr) || (*value == '\0')) {
return default_value;
}
char* end_ptr = nullptr;
uint64_t result = strtoull(value, &end_ptr, 10);
ASSERT(*end_ptr == '\0'); // Parsed full string
return result;
}
};
class IdParameter : public MethodParameter {
public:
IdParameter(const char* name, bool required)
: MethodParameter(name, required) {}
virtual bool Validate(const char* value) const { return (value != nullptr); }
};
class StringParameter : public MethodParameter {
public:
StringParameter(const char* name, bool required)
: MethodParameter(name, required) {}
virtual bool Validate(const char* value) const { return (value != nullptr); }
};
class RunnableIsolateParameter : public MethodParameter {
public:
explicit RunnableIsolateParameter(const char* name)
: MethodParameter(name, true) {}
virtual bool Validate(const char* value) const {
Isolate* isolate = Isolate::Current();
return (value != nullptr) && (isolate != nullptr) &&
(isolate->is_runnable());
}
virtual void PrintError(const char* name,
const char* value,
JSONStream* js) const {
js->PrintError(kIsolateMustBeRunnable,
"Isolate must be runnable before this request is made.");
}
};
class EnumParameter : public MethodParameter {
public:
EnumParameter(const char* name, bool required, const char* const* enums)
: MethodParameter(name, required), enums_(enums) {}
virtual bool Validate(const char* value) const {
if (value == nullptr) {
return true;
}
for (intptr_t i = 0; enums_[i] != nullptr; i++) {
if (strcmp(value, enums_[i]) == 0) {
return true;
}
}
return false;
}
private:
const char* const* enums_;
};
// If the key is not found, this function returns the last element in the
// values array. This can be used to encode the default value.
template <typename T>
T EnumMapper(const char* value, const char* const* enums, T* values) {
ASSERT(value != nullptr);
intptr_t i = 0;
for (i = 0; enums[i] != nullptr; i++) {
if (strcmp(value, enums[i]) == 0) {
return values[i];
}
}
// Default value.
return values[i];
}
typedef void (*ServiceMethodEntry)(Thread* thread, JSONStream* js);
struct ServiceMethodDescriptor {
const char* name;
const ServiceMethodEntry entry;
const MethodParameter* const* parameters;
};
static void PrintMissingParamError(JSONStream* js, const char* param) {
js->PrintError(kInvalidParams, "%s expects the '%s' parameter", js->method(),
param);
}
static void PrintUnrecognizedMethodError(JSONStream* js) {
js->PrintError(kMethodNotFound, nullptr);
}
// TODO(johnmccutchan): Do we reject unexpected parameters?
static bool ValidateParameters(const MethodParameter* const* parameters,
JSONStream* js) {
if (parameters == nullptr) {
return true;
}
if (js->NumObjectParameters() > 0) {
Object& value = Object::Handle();
for (intptr_t i = 0; parameters[i] != nullptr; i++) {
const MethodParameter* parameter = parameters[i];
const char* name = parameter->name();
const bool required = parameter->required();
value = js->LookupObjectParam(name);
const bool has_parameter = !value.IsNull();
if (required && !has_parameter) {
PrintMissingParamError(js, name);
return false;
}
if (has_parameter && !parameter->ValidateObject(value)) {
parameter->PrintErrorObject(name, value, js);
return false;
}
}
} else {
for (intptr_t i = 0; parameters[i] != nullptr; i++) {
const MethodParameter* parameter = parameters[i];
const char* name = parameter->name();
const bool required = parameter->required();
const char* value = js->LookupParam(name);
const bool has_parameter = (value != nullptr);
if (required && !has_parameter) {
PrintMissingParamError(js, name);
return false;
}
if (has_parameter && !parameter->Validate(value)) {
parameter->PrintError(name, value, js);
return false;
}
}
}
return true;
}
void Service::PostError(const String& method_name,
const Array& parameter_keys,
const Array& parameter_values,
const Instance& reply_port,
const Instance& id,
const Error& error) {
Thread* T = Thread::Current();
StackZone zone(T);
JSONStream js;
js.Setup(zone.GetZone(), SendPort::Cast(reply_port).Id(), id, method_name,
parameter_keys, parameter_values);
js.PrintError(kExtensionError, "Error in extension `%s`: %s", js.method(),
error.ToErrorCString());
js.PostReply();
}
ErrorPtr Service::InvokeMethod(Isolate* I,
const Array& msg,
bool parameters_are_dart_objects) {
Thread* T = Thread::Current();
ASSERT(I == T->isolate());
ASSERT(I != nullptr);
ASSERT(T->execution_state() == Thread::kThreadInVM);
ASSERT(!msg.IsNull());
ASSERT(msg.Length() == 6);
{
StackZone zone(T);
Isolate& isolate = *T->isolate();
Instance& reply_port = Instance::Handle(Z);
Instance& seq = String::Handle(Z);
String& method_name = String::Handle(Z);
Array& param_keys = Array::Handle(Z);
Array& param_values = Array::Handle(Z);
reply_port ^= msg.At(1);
seq ^= msg.At(2);
method_name ^= msg.At(3);
param_keys ^= msg.At(4);
param_values ^= msg.At(5);
ASSERT(!method_name.IsNull());
ASSERT(seq.IsNull() || seq.IsString() || seq.IsNumber());
ASSERT(!param_keys.IsNull());
ASSERT(!param_values.IsNull());
ASSERT(param_keys.Length() == param_values.Length());
// We expect a reply port unless there is a null sequence id,
// which indicates that no reply should be sent. We use this in
// tests.
if (!seq.IsNull() && !reply_port.IsSendPort()) {
FATAL("SendPort expected.");
}
JSONStream js;
Dart_Port reply_port_id =
(reply_port.IsNull() ? ILLEGAL_PORT : SendPort::Cast(reply_port).Id());
js.Setup(zone.GetZone(), reply_port_id, seq, method_name, param_keys,
param_values, parameters_are_dart_objects);
// |id_zone| is the zone that will be stored into the |JSONStream| that we
// are about to create, meaning that it is where temporary Service IDs may
// be allocated by the RPC currently being handled.
RingServiceIdZone* id_zone = &isolate.EnsureDefaultServiceIdZone();
const char* id_zone_id_arg = js.LookupParam("idZoneId");
if (id_zone_id_arg != nullptr) {
intptr_t id_zone_id = ServiceIdZone::StringIdToInt(id_zone_id_arg);
if (id_zone_id != -1) {
id_zone = isolate.GetServiceIdZone(id_zone_id);
} else {
id_zone = nullptr;
}
}
if (id_zone == nullptr) {
PrintInvalidParamError(&js, "idZoneId");
js.PostReply();
return T->StealStickyError();
}
js.set_id_zone(*id_zone);
const char* c_method_name = method_name.ToCString();
const ServiceMethodDescriptor* method = FindMethod(c_method_name);
if (method != nullptr) {
if (!ValidateParameters(method->parameters, &js)) {
js.PostReply();
return T->StealStickyError();
}
method->entry(T, &js);
Service::LogResponseSize(c_method_name, &js);
js.PostReply();
return T->StealStickyError();
}
EmbedderServiceHandler* handler = FindIsolateEmbedderHandler(c_method_name);
if (handler == nullptr) {
handler = FindRootEmbedderHandler(c_method_name);
}
if (handler != nullptr) {
EmbedderHandleMessage(handler, &js);
return T->StealStickyError();
}
const Instance& extension_handler =
Instance::Handle(Z, I->LookupServiceExtensionHandler(method_name));
if (!extension_handler.IsNull()) {
ScheduleExtensionHandler(extension_handler, method_name, param_keys,
param_values, reply_port, seq);
// Schedule was successful. Extension code will post a reply
// asynchronously.
return T->StealStickyError();
}
PrintUnrecognizedMethodError(&js);
js.PostReply();
return T->StealStickyError();
}
}
ErrorPtr Service::HandleRootMessage(const Array& msg_instance) {
Isolate* isolate = Isolate::Current();
return InvokeMethod(isolate, msg_instance);
}
ErrorPtr Service::HandleObjectRootMessage(const Array& msg_instance) {
Isolate* isolate = Isolate::Current();
return InvokeMethod(isolate, msg_instance, true);
}
ErrorPtr Service::HandleIsolateMessage(Isolate* isolate, const Array& msg) {
ASSERT(isolate != nullptr);
const Error& error = Error::Handle(InvokeMethod(isolate, msg));
return MaybePause(isolate, error);
}
static void Finalizer(void* isolate_callback_data, void* buffer) {
free(buffer);
}
void Service::SendEvent(const char* stream_id,
const char* event_type,
uint8_t* bytes,
intptr_t bytes_length) {
Thread* thread = Thread::Current();
Isolate* isolate = thread->isolate();
ASSERT(isolate != nullptr);
if (FLAG_trace_service) {
OS::PrintErr(
"vm-service: Pushing ServiceEvent(isolate='%s', "
"isolateId='" ISOLATE_SERVICE_ID_FORMAT_STRING
"', kind='%s',"
" len=%" Pd ") to stream %s\n",
isolate->name(), static_cast<int64_t>(isolate->main_port()), event_type,
bytes_length, stream_id);
}
bool result;
{
Dart_CObject cbytes;
cbytes.type = Dart_CObject_kExternalTypedData;
cbytes.value.as_external_typed_data.type = Dart_TypedData_kUint8;
cbytes.value.as_external_typed_data.length = bytes_length;
cbytes.value.as_external_typed_data.data = bytes;
cbytes.value.as_external_typed_data.peer = bytes;
cbytes.value.as_external_typed_data.callback = Finalizer;
Dart_CObject cstream_id;
cstream_id.type = Dart_CObject_kString;
cstream_id.value.as_string = const_cast<char*>(stream_id);
Dart_CObject* elements[2];
elements[0] = &cstream_id;
elements[1] = &cbytes;
Dart_CObject message;
message.type = Dart_CObject_kArray;
message.value.as_array.length = 2;
message.value.as_array.values = elements;
std::unique_ptr<Message> msg =
WriteApiMessage(thread->zone(), &message, ServiceIsolate::Port(),
Message::kNormalPriority);
if (msg == nullptr) {
result = false;
} else {
result = PortMap::PostMessage(std::move(msg));
}
}
if (!result) {
free(bytes);
}
}
void Service::SendEventWithData(const char* stream_id,
const char* event_type,
intptr_t reservation,
const char* metadata,
intptr_t metadata_size,
uint8_t* data,
intptr_t data_size) {
ASSERT(kInt32Size + metadata_size <= reservation);
// Using a SPACE creates valid JSON. Our goal here is to prevent the memory
// overhead of copying to concatenate metadata and payload together by
// over-allocating to underlying buffer before we know how long the metadata
// will be.
memset(data, ' ', reservation);
reinterpret_cast<uint32_t*>(data)[0] = reservation;
memmove(&(reinterpret_cast<uint32_t*>(data)[1]), metadata, metadata_size);
Service::SendEvent(stream_id, event_type, data, data_size);
}
static void ReportPauseOnConsole(ServiceEvent* event) {
const char* name = event->isolate()->name();
const int64_t main_port = static_cast<int64_t>(event->isolate()->main_port());
switch (event->kind()) {
case ServiceEvent::kPauseStart:
OS::PrintErr("vm-service: isolate(%" Pd64
") '%s' has no debugger attached and is paused at start.",
main_port, name);
break;
case ServiceEvent::kPauseExit:
OS::PrintErr("vm-service: isolate(%" Pd64
") '%s' has no debugger attached and is paused at exit.",
main_port, name);
break;
case ServiceEvent::kPauseException:
OS::PrintErr(
"vm-service: isolate (%" Pd64
") '%s' has no debugger attached and is paused due to exception.",
main_port, name);
break;
case ServiceEvent::kPauseInterrupted:
OS::PrintErr(
"vm-service: isolate (%" Pd64
") '%s' has no debugger attached and is paused due to interrupt.",
main_port, name);
break;
case ServiceEvent::kPauseBreakpoint:
OS::PrintErr("vm-service: isolate (%" Pd64
") '%s' has no debugger attached and is paused.",
main_port, name);
break;
case ServiceEvent::kPausePostRequest:
OS::PrintErr("vm-service: isolate (%" Pd64
") '%s' has no debugger attached and is paused post reload.",
main_port, name);
break;
default:
UNREACHABLE();
break;
}
if (!ServiceIsolate::IsRunning()) {
OS::PrintErr(" Start the vm-service to debug.\n");
} else if (ServiceIsolate::server_address() == nullptr) {
OS::PrintErr(" Connect to the Dart VM service to debug.\n");
} else {
OS::PrintErr(" Connect to the Dart VM service at %s to debug.\n",
ServiceIsolate::server_address());
}
const Error& err = Error::Handle(Thread::Current()->sticky_error());
if (!err.IsNull()) {
OS::PrintErr("%s\n", err.ToErrorCString());
}
}
void Service::HandleEvent(ServiceEvent* event, bool enter_safepoint) {
if (event->stream_info() != nullptr && !event->stream_info()->enabled()) {
if (FLAG_warn_on_pause_with_no_debugger && event->IsPause()) {
// If we are about to pause a running program which has no
// debugger connected, tell the user about it.
ReportPauseOnConsole(event);
}
// Ignore events when no one is listening to the event stream.
return;
} else if (event->stream_info() != nullptr &&
FLAG_warn_on_pause_with_no_debugger && event->IsPause()) {
ReportPauseOnConsole(event);
}
if (!ServiceIsolate::IsRunning()) {
return;
}
JSONStream js;
// When a Service event needs to include temporary object IDs, these IDs will
// be allocated in the default ID zone of the isolate associated with the
// Service event.
if (event->isolate() != nullptr) {
js.set_id_zone(event->isolate()->EnsureDefaultServiceIdZone());
}
if (event->stream_info() != nullptr) {
js.set_include_private_members(
event->stream_info()->include_private_members());
}
const char* stream_id = event->stream_id();
ASSERT(stream_id != nullptr);
{
JSONObject jsobj(&js);
jsobj.AddProperty("jsonrpc", "2.0");
jsobj.AddProperty("method", "streamNotify");
JSONObject params(&jsobj, "params");
params.AddProperty("streamId", stream_id);
params.AddProperty("event", event);
}
PostEvent(event->isolate_group(), event->isolate(), stream_id,
event->KindAsCString(), &js, enter_safepoint);
}
void Service::PostEvent(IsolateGroup* isolate_group,
Isolate* isolate,
const char* stream_id,
const char* kind,
JSONStream* event,
bool enter_safepoint) {
if (enter_safepoint) {
// Enter a safepoint so we don't block the mutator while processing
// large events.
TransitionToNative transition(Thread::Current());
PostEventImpl(isolate_group, isolate, stream_id, kind, event);
return;
}
PostEventImpl(isolate_group, isolate, stream_id, kind, event);
}
void Service::PostEventImpl(IsolateGroup* isolate_group,
Isolate* isolate,
const char* stream_id,
const char* kind,
JSONStream* event) {
ASSERT(stream_id != nullptr);
ASSERT(kind != nullptr);
ASSERT(event != nullptr);
if (FLAG_trace_service) {
if (isolate != nullptr) {
ASSERT(isolate_group != nullptr);
OS::PrintErr(
"vm-service: Pushing "
"ServiceEvent(isolateGroupId='" ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING
"', isolate='%s', isolateId='" ISOLATE_SERVICE_ID_FORMAT_STRING
"', kind='%s') to stream %s\n",
isolate_group->id(), isolate->name(),
static_cast<int64_t>(isolate->main_port()), kind, stream_id);
} else if (isolate_group != nullptr) {
OS::PrintErr(
"vm-service: Pushing "
"ServiceEvent(isolateGroupId='" ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING
"', kind='%s') to stream %s\n",
isolate_group->id(), kind, stream_id);
} else {
OS::PrintErr(
"vm-service: Pushing ServiceEvent(isolate='<no current isolate>', "
"kind='%s') to stream %s\n",
kind, stream_id);
}
}
Service::LogResponseSize(kind, event);
// Message is of the format [<stream id>, <json string>].
//
// Build the event message in the C heap to avoid dart heap
// allocation. This method can be called while we have acquired a
// direct pointer to typed data, so we can't allocate here.
Dart_CObject list_cobj;
Dart_CObject* list_values[2];
list_cobj.type = Dart_CObject_kArray;
list_cobj.value.as_array.length = 2;
list_cobj.value.as_array.values = list_values;
Dart_CObject stream_id_cobj;
stream_id_cobj.type = Dart_CObject_kString;
stream_id_cobj.value.as_string = const_cast<char*>(stream_id);
list_values[0] = &stream_id_cobj;
Dart_CObject json_cobj;
json_cobj.type = Dart_CObject_kString;
json_cobj.value.as_string = const_cast<char*>(event->ToCString());
list_values[1] = &json_cobj;
AllocOnlyStackZone zone;
std::unique_ptr<Message> msg =
WriteApiMessage(zone.GetZone(), &list_cobj, ServiceIsolate::Port(),
Message::kNormalPriority);
if (msg != nullptr) {
PortMap::PostMessage(std::move(msg));
}
}
class EmbedderServiceHandler {
public:
explicit EmbedderServiceHandler(const char* name)
: name_(nullptr),
callback_(nullptr),
user_data_(nullptr),
next_(nullptr) {
ASSERT(name != nullptr);
name_ = Utils::StrDup(name);
}
~EmbedderServiceHandler() { free(name_); }
const char* name() const { return name_; }
Dart_ServiceRequestCallback callback() const { return callback_; }
void set_callback(Dart_ServiceRequestCallback callback) {
callback_ = callback;
}
void* user_data() const { return user_data_; }
void set_user_data(void* user_data) { user_data_ = user_data; }
EmbedderServiceHandler* next() const { return next_; }
void set_next(EmbedderServiceHandler* next) { next_ = next; }
private:
char* name_;
Dart_ServiceRequestCallback callback_;
void* user_data_;
EmbedderServiceHandler* next_;
};
void Service::EmbedderHandleMessage(EmbedderServiceHandler* handler,
JSONStream* js) {
ASSERT(handler != nullptr);
Dart_ServiceRequestCallback callback = handler->callback();
ASSERT(callback != nullptr);
const char* response = nullptr;
bool success;
{
TransitionVMToNative transition(Thread::Current());
success = callback(js->method(), js->param_keys(), js->param_values(),
js->num_params(), handler->user_data(), &response);
}
ASSERT(response != nullptr);
if (!success) {
js->SetupError();
}
js->buffer()->AddString(response);
js->PostReply();
free(const_cast<char*>(response));
}
void Service::RegisterIsolateEmbedderCallback(
const char* name,
Dart_ServiceRequestCallback callback,
void* user_data) {
if (name == nullptr) {
return;
}
EmbedderServiceHandler* handler = FindIsolateEmbedderHandler(name);
if (handler != nullptr) {
// Update existing handler entry.
handler->set_callback(callback);
handler->set_user_data(user_data);
return;
}
// Create a new handler.
handler = new EmbedderServiceHandler(name);
handler->set_callback(callback);
handler->set_user_data(user_data);
// Insert into isolate_service_handler_head_ list.
handler->set_next(isolate_service_handler_head_);
isolate_service_handler_head_ = handler;
}
EmbedderServiceHandler* Service::FindIsolateEmbedderHandler(const char* name) {
EmbedderServiceHandler* current = isolate_service_handler_head_;
while (current != nullptr) {
if (strcmp(name, current->name()) == 0) {
return current;
}
current = current->next();
}
return nullptr;
}
void Service::RegisterRootEmbedderCallback(const char* name,
Dart_ServiceRequestCallback callback,
void* user_data) {
if (name == nullptr) {
return;
}
EmbedderServiceHandler* handler = FindRootEmbedderHandler(name);
if (handler != nullptr) {
// Update existing handler entry.
handler->set_callback(callback);
handler->set_user_data(user_data);
return;
}
// Create a new handler.
handler = new EmbedderServiceHandler(name);
handler->set_callback(callback);
handler->set_user_data(user_data);
// Insert into root_service_handler_head_ list.
handler->set_next(root_service_handler_head_);
root_service_handler_head_ = handler;
}
void Service::SetEmbedderStreamCallbacks(
Dart_ServiceStreamListenCallback listen_callback,
Dart_ServiceStreamCancelCallback cancel_callback) {
stream_listen_callback_ = listen_callback;
stream_cancel_callback_ = cancel_callback;
}
void Service::SetGetServiceAssetsCallback(
Dart_GetVMServiceAssetsArchive get_service_assets) {
get_service_assets_callback_ = get_service_assets;
}
void Service::SetEmbedderInformationCallback(
Dart_EmbedderInformationCallback callback) {
embedder_information_callback_ = callback;
}
int64_t Service::CurrentRSS() {
if (embedder_information_callback_ == nullptr) {
return -1;
}
Dart_EmbedderInformation info = {
0, // version
nullptr, // name
0, // max_rss
0 // current_rss
};
embedder_information_callback_(&info);
ASSERT(info.version == DART_EMBEDDER_INFORMATION_CURRENT_VERSION);
return info.current_rss;
}
int64_t Service::MaxRSS() {
if (embedder_information_callback_ == nullptr) {
return -1;
}
Dart_EmbedderInformation info = {
0, // version
nullptr, // name
0, // max_rss
0 // current_rss
};
embedder_information_callback_(&info);
ASSERT(info.version == DART_EMBEDDER_INFORMATION_CURRENT_VERSION);
return info.max_rss;
}
void Service::SetDartLibraryKernelForSources(const uint8_t* kernel_bytes,
intptr_t kernel_length) {
dart_library_kernel_ = kernel_bytes;
dart_library_kernel_len_ = kernel_length;
}
EmbedderServiceHandler* Service::FindRootEmbedderHandler(const char* name) {
EmbedderServiceHandler* current = root_service_handler_head_;
while (current != nullptr) {
if (strcmp(name, current->name()) == 0) {
return current;
}
current = current->next();
}
return nullptr;
}
void Service::ScheduleExtensionHandler(const Instance& handler,
const String& method_name,
const Array& parameter_keys,
const Array& parameter_values,
const Instance& reply_port,
const Instance& id) {
ASSERT(!handler.IsNull());
ASSERT(!method_name.IsNull());
ASSERT(!parameter_keys.IsNull());
ASSERT(!parameter_values.IsNull());
ASSERT(!reply_port.IsNull());
Isolate* isolate = Isolate::Current();
ASSERT(isolate != nullptr);
isolate->AppendServiceExtensionCall(handler, method_name, parameter_keys,
parameter_values, reply_port, id);
}
static const MethodParameter* const get_isolate_params[] = {
ISOLATE_PARAMETER,
nullptr,
};
static void GetIsolate(Thread* thread, JSONStream* js) {
thread->isolate()->PrintJSON(js, false);
}
static const MethodParameter* const get_isolate_group_params[] = {
ISOLATE_GROUP_PARAMETER,
nullptr,
};
enum SentinelType {
kCollectedSentinel,
kExpiredSentinel,
kFreeSentinel,
};
static void PrintSentinel(JSONStream* js, SentinelType sentinel_type) {
JSONObject jsobj(js);
jsobj.AddProperty("type", "Sentinel");
switch (sentinel_type) {
case kCollectedSentinel:
jsobj.AddProperty("kind", "Collected");
jsobj.AddProperty("valueAsString", "<collected>");
break;
case kExpiredSentinel:
jsobj.AddProperty("kind", "Expired");
jsobj.AddProperty("valueAsString", "<expired>");
break;
case kFreeSentinel:
jsobj.AddProperty("kind", "Free");
jsobj.AddProperty("valueAsString", "<free>");
break;
default:
UNIMPLEMENTED();
break;
}
}
static const MethodParameter* const
set_stream_include_private_members_params[] = {
NO_ISOLATE_PARAMETER,
new BoolParameter("includePrivateMembers", true),
nullptr,
};
static void SetStreamIncludePrivateMembers(Thread* thread, JSONStream* js) {
const char* stream_id = js->LookupParam("streamId");
if (stream_id == nullptr) {
PrintMissingParamError(js, "streamId");
return;
}
bool include_private_members =
BoolParameter::Parse(js->LookupParam("includePrivateMembers"), false);
intptr_t num_streams = sizeof(streams_) / sizeof(streams_[0]);
for (intptr_t i = 0; i < num_streams; i++) {
if (strcmp(stream_id, streams_[i]->id()) == 0) {
streams_[i]->set_include_private_members(include_private_members);
break;
}
}
PrintSuccess(js);
}
static void ActOnIsolateGroup(JSONStream* js,
std::function<void(IsolateGroup*)> visitor) {
const String& prefix =
String::Handle(String::New(ISOLATE_GROUP_SERVICE_ID_PREFIX));
const String& s =
String::Handle(String::New(js->LookupParam("isolateGroupId")));
if (!s.StartsWith(prefix)) {
PrintInvalidParamError(js, "isolateGroupId");
return;
}
uint64_t isolate_group_id = UInt64Parameter::Parse(
String::Handle(String::SubString(s, prefix.Length())).ToCString());
IsolateGroup::RunWithIsolateGroup(
isolate_group_id,
[&visitor](IsolateGroup* isolate_group) { visitor(isolate_group); },
/*if_not_found=*/[&js]() { PrintSentinel(js, kExpiredSentinel); });
}
static void GetIsolateGroup(Thread* thread, JSONStream* js) {
ActOnIsolateGroup(js, [&](IsolateGroup* isolate_group) {
isolate_group->PrintJSON(js, false);
});
}
static const MethodParameter* const get_memory_usage_params[] = {
ISOLATE_PARAMETER,
nullptr,
};
static void GetMemoryUsage(Thread* thread, JSONStream* js) {
thread->isolate()->PrintMemoryUsageJSON(js);
}
static const MethodParameter* const get_isolate_group_memory_usage_params[] = {
ISOLATE_GROUP_PARAMETER,
nullptr,
};
static void GetIsolateGroupMemoryUsage(Thread* thread, JSONStream* js) {
ActOnIsolateGroup(js, [&](IsolateGroup* isolate_group) {
isolate_group->PrintMemoryUsageJSON(js);
});
}
static const MethodParameter* const get_isolate_pause_event_params[] = {
ISOLATE_PARAMETER,
nullptr,
};
static void GetIsolatePauseEvent(Thread* thread, JSONStream* js) {
thread->isolate()->PrintPauseEventJSON(js);
}
static const MethodParameter* const get_scripts_params[] = {
RUNNABLE_ISOLATE_PARAMETER,
nullptr,
};
static void GetScripts(Thread* thread, JSONStream* js) {
auto object_store = thread->isolate_group()->object_store();
Zone* zone = thread->zone();
const auto& libs =
GrowableObjectArray::Handle(zone, object_store->libraries());
intptr_t num_libs = libs.Length();
Library& lib = Library::Handle(zone);
Array& scripts = Array::Handle(zone);
Script& script = Script::Handle(zone);
JSONObject jsobj(js);
{
jsobj.AddProperty("type", "ScriptList");
JSONArray script_array(&jsobj, "scripts");
for (intptr_t i = 0; i < num_libs; i++) {
lib ^= libs.At(i);
ASSERT(!lib.IsNull());
scripts = lib.LoadedScripts();
for (intptr_t j = 0; j < scripts.Length(); j++) {
script ^= scripts.At(j);
ASSERT(!script.IsNull());
script_array.AddValue(script);
}
}
}
}
static const MethodParameter* const get_stack_params[] = {
RUNNABLE_ISOLATE_PARAMETER,
new UIntParameter("limit", false),
nullptr,
};
static void GetStack(Thread* thread, JSONStream* js) {
if (CheckDebuggerDisabled(thread, js)) {
return;
}
intptr_t limit = 0;
bool has_limit = js->HasParam("limit");
if (has_limit) {
limit = UIntParameter::Parse(js->LookupParam("limit"));
if (limit < 0) {
PrintInvalidParamError(js, "limit");
return;
}
}
Isolate* isolate = thread->isolate();
DebuggerStackTrace* stack = isolate->debugger()->StackTrace();
DebuggerStackTrace* async_awaiter_stack =
isolate->debugger()->AsyncAwaiterStackTrace();
// Do we want the complete script object and complete local variable objects?
// This is true for dump requests.
JSONObject jsobj(js);
jsobj.AddProperty("type", "Stack");
{
JSONArray jsarr(&jsobj, "frames");
intptr_t num_frames =
has_limit ? Utils::Minimum(stack->Length(), limit) : stack->Length();
for (intptr_t i = 0; i < num_frames; i++) {
ActivationFrame* frame = stack->FrameAt(i);
JSONObject jsobj(&jsarr);
frame->PrintToJSONObject(&jsobj);
jsobj.AddProperty("index", i);
}
}
if (async_awaiter_stack != nullptr) {
JSONArray jsarr(&jsobj, "asyncCausalFrames");
intptr_t num_frames =
has_limit ? Utils::Minimum(async_awaiter_stack->Length(), limit)
: async_awaiter_stack->Length();
for (intptr_t i = 0; i < num_frames; i++) {
ActivationFrame* frame = async_awaiter_stack->FrameAt(i);
JSONObject jsobj(&jsarr);
frame->PrintToJSONObject(&jsobj);
jsobj.AddProperty("index", i);
}
}
const bool truncated =
(has_limit &&
(limit < stack->Length() || (async_awaiter_stack != nullptr &&
limit < async_awaiter_stack->Length())));
jsobj.AddProperty("truncated", truncated);
{
MessageHandler::AcquiredQueues aq(isolate->message_handler());
jsobj.AddProperty("messages", aq.queue());
}
}
static void HandleCommonEcho(JSONObject* jsobj, JSONStream* js) {
jsobj->AddProperty("type", "_EchoResponse");
if (js->HasParam("text")) {
jsobj->AddProperty("text", js->LookupParam("text"));
}
}
void Service::SendEchoEvent(Isolate* isolate, const char* text) {
JSONStream js;
{
JSONObject jsobj(&js);
jsobj.AddProperty("jsonrpc", "2.0");
jsobj.AddProperty("method", "streamNotify");
{
JSONObject params(&jsobj, "params");
params.AddProperty("streamId", echo_stream.id());
{
JSONObject event(&params, "event");
event.AddProperty("type", "Event");
event.AddProperty("kind", "_Echo");
event.AddProperty("isolate", isolate);
if (text != nullptr) {
event.AddProperty("text", text);
}
event.AddPropertyTimeMillis("timestamp", OS::GetCurrentTimeMillis());
}
}
}
intptr_t reservation = js.buffer()->length() + sizeof(int32_t);
intptr_t data_size = reservation + 3;
uint8_t* data = reinterpret_cast<uint8_t*>(malloc(data_size));
data[reservation + 0] = 0;
data[reservation + 1] = 128;
data[reservation + 2] = 255;
SendEventWithData(echo_stream.id(), "_Echo", reservation,
js.buffer()->buffer(), js.buffer()->length(), data,
data_size);
}
static void TriggerEchoEvent(Thread* thread, JSONStream* js) {
if (Service::echo_stream.enabled()) {
Service::SendEchoEvent(thread->isolate(), js->LookupParam("text"));
}
JSONObject jsobj(js);
HandleCommonEcho(&jsobj, js);
}
static void Echo(Thread* thread, JSONStream* js) {
JSONObject jsobj(js);
HandleCommonEcho(&jsobj, js);
}
static bool ContainsNonInstance(const Object& obj) {
if (obj.IsArray()) {
const Array& array = Array::Cast(obj);
Object& element = Object::Handle();
for (intptr_t i = 0; i < array.Length(); ++i) {
element = array.At(i);
if (!(element.IsInstance() || element.IsNull())) {
return true;
}
}
return false;
} else if (obj.IsGrowableObjectArray()) {
const GrowableObjectArray& array = GrowableObjectArray::Cast(obj);
Object& element = Object::Handle();
for (intptr_t i = 0; i < array.Length(); ++i) {
element = array.At(i);
if (!(element.IsInstance() || element.IsNull())) {
return true;
}
}
return false;
} else {
return !(obj.IsInstance() || obj.IsNull());
}
}
static ObjectPtr LookUpObjectByServiceId(
Thread* thread,
const char* id_zone_part_of_service_id_as_string,
const char* object_part_of_service_id_as_string,
ObjectIdRing::LookupResult* kind) {
ASSERT(thread != nullptr);
// First, check if the provided ID is a fixed Service ID.
*kind = ObjectIdRing::kValid;
if (strncmp(object_part_of_service_id_as_string, "int-", 4) == 0) {
object_part_of_service_id_as_string += 4;
int64_t value = 0;
if (!OS::StringToInt64(object_part_of_service_id_as_string, &value) ||
!Smi::IsValid(value)) {
*kind = ObjectIdRing::kInvalid;
return Object::null();
}
const Integer& obj =
Integer::Handle(thread->zone(), Smi::New(static_cast<intptr_t>(value)));
return obj.ptr();
} else if (strcmp(object_part_of_service_id_as_string, "bool-true") == 0) {
return Bool::True().ptr();
} else if (strcmp(object_part_of_service_id_as_string, "bool-false") == 0) {
return Bool::False().ptr();
} else if (strcmp(object_part_of_service_id_as_string, "null") == 0) {
return Object::null();
}
// The provided ID is not a fixed Service ID, now we will interpret the ID as
// a temporary Service ID. Temporary Service IDs of objects look like
// "objects/1123/4", where 4 is the ID of the ID Zone where the ID was
// allocated, and 1123 is the part that identifies an object within zone 4.
intptr_t id_zone_part_of_service_id = -1;
if (!GetIntegerId(id_zone_part_of_service_id_as_string,
&id_zone_part_of_service_id)) {
*kind = ObjectIdRing::kInvalid;
return Object::null();
}
intptr_t object_part_of_service_id = -1;
if (!GetIntegerId(object_part_of_service_id_as_string,
&object_part_of_service_id)) {
*kind = ObjectIdRing::kInvalid;
return Object::null();
}
ASSERT(thread->isolate() != nullptr);
const Isolate& isolate = *thread->isolate();
if (id_zone_part_of_service_id >= isolate.NumServiceIdZones()) {
*kind = ObjectIdRing::kInvalid;
return Object::null();
}
RingServiceIdZone& id_zone =
*isolate.GetServiceIdZone(id_zone_part_of_service_id);
return id_zone.GetObjectForId(object_part_of_service_id, kind);
}
static ObjectPtr LookupClassMembers(Thread* thread,
const Class& klass,
char** parts,
int num_parts) {
auto zone = thread->zone();
if (num_parts != 4) {
return Object::sentinel().ptr();
}
const char* encoded_id = parts[3];
auto& id = String::Handle(String::New(encoded_id));
id = String::DecodeIRI(id);
if (id.IsNull()) {
return Object::sentinel().ptr();
}
if (strcmp(parts[2], "fields") == 0) {
// Field ids look like: "classes/17/fields/name"
const auto& field = Field::Handle(klass.LookupField(id));
if (field.IsNull()) {
return Object::sentinel().ptr();
}
return field.ptr();
}
if (strcmp(parts[2], "field_inits") == 0) {
// Field initializer ids look like: "classes/17/field_inits/name"
const auto& field = Field::Handle(klass.LookupField(id));
if (field.IsNull() || (field.is_late() && !field.has_initializer())) {
return Object::sentinel().ptr();
}
const auto& function = Function::Handle(field.EnsureInitializerFunction());
if (function.IsNull()) {
return Object::sentinel().ptr();
}
return function.ptr();
}
if (strcmp(parts[2], "functions") == 0) {
// Function ids look like: "classes/17/functions/name"
const auto& function =
Function::Handle(Resolver::ResolveFunction(zone, klass, id));
if (function.IsNull()) {
return Object::sentinel().ptr();
}
return function.ptr();
}
if (strcmp(parts[2], "implicit_closures") == 0) {
// Function ids look like: "classes/17/implicit_closures/11"
intptr_t id;
if (!GetIntegerId(parts[3], &id)) {
return Object::sentinel().ptr();
}
const auto& func =
Function::Handle(zone, klass.ImplicitClosureFunctionFromIndex(id));
if (func.IsNull()) {
return Object::sentinel().ptr();
}
return func.ptr();
}
if (strcmp(parts[2], "dispatchers") == 0) {
// Dispatcher Function ids look like: "classes/17/dispatchers/11"
intptr_t id;
if (!GetIntegerId(parts[3], &id)) {
return Object::sentinel().ptr();
}
const auto& func =
Function::Handle(zone, klass.InvocationDispatcherFunctionFromIndex(id));
if (func.IsNull()) {
return Object::sentinel().ptr();
}
return func.ptr();
}
if (strcmp(parts[2], "closures") == 0) {
// Closure ids look like: "classes/17/closures/11"
intptr_t id;
if (!GetIntegerId(parts[3], &id)) {
return Object::sentinel().ptr();
}
Function& func = Function::Handle(zone);
func = ClosureFunctionsCache::ClosureFunctionFromIndex(id);
if (func.IsNull()) {
return Object::sentinel().ptr();
}
return func.ptr();
}
UNREACHABLE();
return Object::sentinel().ptr();
}
static ObjectPtr LookupHeapObjectLibraries(IsolateGroup* isolate_group,
char** parts,
int num_parts) {
// Library ids look like "libraries/35"
if (num_parts < 2) {
return Object::sentinel().ptr();
}
const auto& libs =
GrowableObjectArray::Handle(isolate_group->object_store()->libraries());
ASSERT(!libs.IsNull());
const String& id = String::Handle(String::New(parts[1]));
// Scan for private key.
String& private_key = String::Handle();
Library& lib = Library::Handle();
bool lib_found = false;
for (intptr_t i = 0; i < libs.Length(); i++) {
lib ^= libs.At(i);
ASSERT(!lib.IsNull());
private_key = lib.private_key();
if (private_key.Equals(id)) {
lib_found = true;
break;
}
}
if (!lib_found) {
return Object::sentinel().ptr();
}
const auto& klass = Class::Handle(lib.toplevel_class());
ASSERT(!klass.IsNull());
if (num_parts == 2) {
return lib.ptr();
}
if (strcmp(parts[2], "fields") == 0) {
// Library field ids look like: "libraries/17/fields/name"
return LookupClassMembers(Thread::Current(), klass, parts, num_parts);
}
if (strcmp(parts[2], "field_inits") == 0) {
// Library field ids look like: "libraries/17/field_inits/name"
return LookupClassMembers(Thread::Current(), klass, parts, num_parts);
}
if (strcmp(parts[2], "functions") == 0) {
// Library function ids look like: "libraries/17/functions/name"
return LookupClassMembers(Thread::Current(), klass, parts, num_parts);
}
if (strcmp(parts[2], "closures") == 0) {
// Library function ids look like: "libraries/17/closures/name"
return LookupClassMembers(Thread::Current(), klass, parts, num_parts);
}
if (strcmp(parts[2], "implicit_closures") == 0) {
// Library function ids look like: "libraries/17/implicit_closures/name"
return LookupClassMembers(Thread::Current(), klass, parts, num_parts);
}
if (strcmp(parts[2], "scripts") == 0) {
// Script ids look like "libraries/35/scripts/library%2Furl.dart/12345"
if (num_parts != 5) {
return Object::sentinel().ptr();
}
const String& id = String::Handle(String::New(parts[3]));
ASSERT(!id.IsNull());
// The id is the url of the script % encoded, decode it.
const String& requested_url = String::Handle(String::DecodeIRI(id));
// Each script id is tagged with a load time.
int64_t timestamp;
if (!GetInteger64Id(parts[4], &timestamp, 16) || (timestamp < 0)) {
return Object::sentinel().ptr();
}
Script& script = Script::Handle();
String& script_url = String::Handle();
const Array& loaded_scripts = Array::Handle(lib.LoadedScripts());
ASSERT(!loaded_scripts.IsNull());
intptr_t i;
for (i = 0; i < loaded_scripts.Length(); i++) {
script ^= loaded_scripts.At(i);
ASSERT(!script.IsNull());
script_url = script.url();
if (script_url.Equals(requested_url) &&
(timestamp == script.load_timestamp())) {
return script.ptr();
}
}
}
// Not found.
return Object::sentinel().ptr();
}
static ObjectPtr LookupHeapObjectClasses(Thread* thread,
char** parts,
int num_parts) {
// Class ids look like: "classes/17"
if (num_parts < 2) {
return Object::sentinel().ptr();
}
Zone* zone = thread->zone();
auto table = thread->isolate_group()->class_table();
intptr_t id;
if (!GetIntegerId(parts[1], &id) || !table->IsValidIndex(id)) {
return Object::sentinel().ptr();
}
Class& cls = Class::Handle(zone, table->At(id));
if (num_parts == 2) {
return cls.ptr();
}
if (strcmp(parts[2], "closures") == 0) {
// Closure ids look like: "classes/17/closures/11"
return LookupClassMembers(thread, cls, parts, num_parts);
} else if (strcmp(parts[2], "field_inits") == 0) {
// Field initializer ids look like: "classes/17/field_inits/name"
return LookupClassMembers(thread, cls, parts, num_parts);
} else if (strcmp(parts[2], "fields") == 0) {
// Field ids look like: "classes/17/fields/name"
return LookupClassMembers(thread, cls, parts, num_parts);
} else if (strcmp(parts[2], "functions") == 0) {
// Function ids look like: "classes/17/functions/name"
return LookupClassMembers(thread, cls, parts, num_parts);
} else if (strcmp(parts[2], "implicit_closures") == 0) {
// Function ids look like: "classes/17/implicit_closures/11"
return LookupClassMembers(thread, cls, parts, num_parts);
} else if (strcmp(parts[2], "dispatchers") == 0) {
// Dispatcher Function ids look like: "classes/17/dispatchers/11"
return LookupClassMembers(thread, cls, parts, num_parts);
} else if (strcmp(parts[2], "types") == 0) {
// Type ids look like: "classes/17/types/11"
if (num_parts != 4) {
return Object::sentinel().ptr();
}
intptr_t id;
if (!GetIntegerId(parts[3], &id)) {
return Object::sentinel().ptr();
}
if (id != 0) {
return Object::sentinel().ptr();
}
const Type& type = Type::Handle(zone, cls.DeclarationType());
if (!type.IsNull()) {
return type.ptr();
}
}
// Not found.
return Object::sentinel().ptr();
}
static ObjectPtr LookupHeapObjectTypeArguments(Thread* thread,
char** parts,
int num_parts) {
// TypeArguments ids look like: "typearguments/17"
if (num_parts < 2) {
return Object::sentinel().ptr();
}
intptr_t id;
if (!GetIntegerId(parts[1], &id)) {
return Object::sentinel().ptr();
}
ObjectStore* object_store = thread->isolate_group()->object_store();
const Array& table =
Array::Handle(thread->zone(), object_store->canonical_type_arguments());
ASSERT(table.Length() > 0);
const intptr_t table_size = table.Length() - 1;
if ((id < 0) || (id >= table_size) || (table.At(id) == Object::null())) {
return Object::sentinel().ptr();
}
return table.At(id);
}
static ObjectPtr LookupHeapObjectCode(char** parts, int num_parts) {
if (num_parts != 2) {
return Object::sentinel().ptr();
}
uword pc;
const char* const kCollectedPrefix = "collected-";
const intptr_t kCollectedPrefixLen = strlen(kCollectedPrefix);
const char* const kNativePrefix = "native-";
const intptr_t kNativePrefixLen = strlen(kNativePrefix);
const char* const kReusedPrefix = "reused-";
const intptr_t kReusedPrefixLen = strlen(kReusedPrefix);
const char* id = parts[1];
if (strncmp(kCollectedPrefix, id, kCollectedPrefixLen) == 0) {
if (!GetUnsignedIntegerId(&id[kCollectedPrefixLen], &pc, 16)) {
return Object::sentinel().ptr();
}
// TODO(turnidge): Return "collected" instead.
return Object::null();
}
if (strncmp(kNativePrefix, id, kNativePrefixLen) == 0) {
if (!GetUnsignedIntegerId(&id[kNativePrefixLen], &pc, 16)) {
return Object::sentinel().ptr();
}
// TODO(johnmccutchan): Support native Code.
return Object::null();
}
if (strncmp(kReusedPrefix, id, kReusedPrefixLen) == 0) {
if (!GetUnsignedIntegerId(&id[kReusedPrefixLen], &pc, 16)) {
return Object::sentinel().ptr();
}
// TODO(turnidge): Return "expired" instead.
return Object::null();
}
int64_t timestamp = 0;
if (!GetCodeId(id, &timestamp, &pc) || (timestamp < 0)) {
return Object::sentinel().ptr();
}
Code& code = Code::Handle(Code::FindCode(pc, timestamp));
if (!code.IsNull()) {
return code.ptr();
}
// Not found.
return Object::sentinel().ptr();
}
static ObjectPtr LookupHeapObjectMessage(Thread* thread,
char** parts,
int num_parts) {
if (num_parts != 2) {
return Object::sentinel().ptr();
}
uword message_id = 0;
if (!GetUnsignedIntegerId(parts[1], &message_id, 16)) {
return Object::sentinel().ptr();
}
MessageHandler::AcquiredQueues aq(thread->isolate()->message_handler());
Message* message = aq.queue()->FindMessageById(message_id);
if (message == nullptr) {
// The user may try to load an expired message.
return Object::sentinel().ptr();
}
if (message->IsRaw()) {
return message->raw_obj();
} else {
return ReadMessage(thread, message);
}
}
static ObjectPtr LookupHeapObject(Thread* thread,
const char* id_original,
ObjectIdRing::LookupResult* result) {
char* id = thread->zone()->MakeCopyOfString(id_original);
// Parse the id by splitting at each '/'.
const int MAX_PARTS = 8;
char* parts[MAX_PARTS];
int num_parts = 0;
int i = 0;
int start_pos = 0;
while (id[i] != '\0') {
if (id[i] == '/') {
id[i++] = '\0';
parts[num_parts++] = &id[start_pos];
if (num_parts == MAX_PARTS) {
break;
}
start_pos = i;
} else {
i++;
}
}
if (num_parts < MAX_PARTS) {
parts[num_parts++] = &id[start_pos];
}
if (result != nullptr) {
*result = ObjectIdRing::kValid;
}
Isolate* isolate = thread->isolate();
if (strcmp(parts[0], "objects") == 0) {
Object& obj = Object::Handle(thread->zone());
ObjectIdRing::LookupResult lookup_result;
obj = LookUpObjectByServiceId(thread, parts[2], parts[1], &lookup_result);
if (lookup_result != ObjectIdRing::kValid) {
if (result != nullptr) {
*result = lookup_result;
}
return Object::sentinel().ptr();
}
return obj.ptr();
} else if (strcmp(parts[0], "libraries") == 0) {
return LookupHeapObjectLibraries(isolate->group(), parts, num_parts);
} else if (strcmp(parts[0], "classes") == 0) {
return LookupHeapObjectClasses(thread, parts, num_parts);
} else if (strcmp(parts[0], "typearguments") == 0) {
return LookupHeapObjectTypeArguments(thread, parts, num_parts);
} else if (strcmp(parts[0], "code") == 0) {
return LookupHeapObjectCode(parts, num_parts);
} else if (strcmp(parts[0], "messages") == 0) {
return LookupHeapObjectMessage(thread, parts, num_parts);
}
// Not found.
return Object::sentinel().ptr();
}
static Breakpoint* LookupBreakpoint(Isolate* isolate,
const char* id,
ObjectIdRing::LookupResult* result) {
*result = ObjectIdRing::kInvalid;
size_t end_pos = strcspn(id, "/");
if (end_pos == strlen(id)) {
return nullptr;
}
const char* rest = id + end_pos + 1; // +1 for '/'.
if (strncmp("breakpoints", id, end_pos) == 0) {
intptr_t bpt_id = 0;
Breakpoint* bpt = nullptr;
if (GetIntegerId(rest, &bpt_id)) {
bpt = isolate->debugger()->GetBreakpointById(bpt_id);
if (bpt != nullptr) {
*result = ObjectIdRing::kValid;
return bpt;
}
if (bpt_id < isolate->debugger()->limitBreakpointId()) {
*result = ObjectIdRing::kCollected;
return nullptr;
}
}
}
return nullptr;
}
static inline void AddParentFieldToResponseBasedOnRecord(
Thread* thread,
Array* field_names_handle,
String* name_handle,
const JSONObject& jsresponse,
const Record& record,
const intptr_t field_slot_offset) {
*field_names_handle = record.GetFieldNames(thread);
const intptr_t num_positional_fields =
record.num_fields() - field_names_handle->Length();
const intptr_t field_index =
(field_slot_offset - Record::field_offset(0)) / Record::kBytesPerElement;
if (field_index < num_positional_fields) {
jsresponse.AddProperty("parentField", field_index);
} else {
*name_handle ^= field_names_handle->At(field_index - num_positional_fields);
jsresponse.AddProperty("parentField", name_handle->ToCString());
}
}
static void PrintInboundReferences(Thread* thread,
Object* target,
intptr_t limit,
JSONStream* js) {
ObjectGraph graph(thread);
Array& path = Array::Handle(Array::New(limit * 2));
intptr_t length = graph.InboundReferences(target, path);
OffsetsTable offsets_table(thread->zone());
JSONObject jsobj(js);
jsobj.AddProperty("type", "InboundReferences");
{
JSONArray elements(&jsobj, "references");
Object& source = Object::Handle();
Smi& slot_offset = Smi::Handle();
Array& field_names = Array::Handle();
String& name = String::Handle();
Class& source_class = Class::Handle();
Field& field = Field::Handle();
Array& parent_field_map = Array::Handle();
limit = Utils::Minimum(limit, length);
for (intptr_t i = 0; i < limit; ++i) {
JSONObject jselement(&elements);
source = path.At(i * 2);
slot_offset ^= path.At((i * 2) + 1);
jselement.AddProperty("source", source);
if (source.IsArray()) {
intptr_t element_index =
(slot_offset.Value() - Array::element_offset(0)) /
Array::kBytesPerElement;
jselement.AddProperty("parentListIndex", element_index);
jselement.AddProperty("parentField", element_index);
} else if (source.IsRecord()) {
AddParentFieldToResponseBasedOnRecord(thread, &field_names, &name,
jselement, Record::Cast(source),
slot_offset.Value());
} else {
if (source.IsInstance()) {
source_class = source.clazz();
parent_field_map = source_class.OffsetToFieldMap();
intptr_t index = slot_offset.Value() >> kCompressedWordSizeLog2;
if (index > 0 && index < parent_field_map.Length()) {
field ^= parent_field_map.At(index);
if (!field.IsNull()) {
jselement.AddProperty("parentField", field);
continue;
}
}
}
const char* field_name = offsets_table.FieldNameForOffset(
source.GetClassId(), slot_offset.Value());
if (field_name != nullptr) {
jselement.AddProperty("_parentWordOffset", slot_offset.Value());
// TODO(vm-service): Adjust RPC type to allow returning a field name
// without a field object, or reify the fields described by
// raw_object_fields.cc
// jselement.AddProperty("_parentFieldName", field_name);
} else if (source.IsContext()) {
intptr_t element_index =
(slot_offset.Value() - Context::variable_offset(0)) /
Context::kBytesPerElement;
jselement.AddProperty("parentListIndex", element_index);
jselement.AddProperty("parentField", element_index);
} else {
jselement.AddProperty("_parentWordOffset", slot_offset.Value());
}
}
}
}
// We nil out the array after generating the response to prevent
// reporting spurious references when repeatedly looking for the
// references to an object.
for (intptr_t i = 0; i < path.Length(); i++) {
path.SetAt(i, Object::null_object());
}
}
static const MethodParameter* const get_inbound_references_params[] = {
RUNNABLE_ISOLATE_PARAMETER,
nullptr,
};
static void GetInboundReferences(Thread* thread, JSONStream* js) {
const char* target_id = js->LookupParam("targetId");
if (target_id == nullptr) {
PrintMissingParamError(js, "targetId");
return;
}
const char* limit_cstr = js->LookupParam("limit");
if (limit_cstr == nullptr) {
PrintMissingParamError(js, "limit");
return;
}
intptr_t limit;
if (!GetIntegerId(limit_cstr, &limit)) {
PrintInvalidParamError(js, "limit");
return;
}
Object& obj = Object::Handle(thread->zone());
ObjectIdRing::LookupResult lookup_result;
{
HANDLESCOPE(thread);
obj = LookupHeapObject(thread, target_id, &lookup_result);
}
if (obj.ptr() == Object::sentinel().ptr()) {
if (lookup_result == ObjectIdRing::kCollected) {
PrintSentinel(js, kCollectedSentinel);
} else if (lookup_result == ObjectIdRing::kExpired) {
PrintSentinel(js, kExpiredSentinel);
} else {
PrintInvalidParamError(js, "targetId");
}
return;
}
PrintInboundReferences(thread, &obj, limit, js);
}
static void PrintRetainingPath(Thread* thread,
Object* obj,
intptr_t limit,
JSONStream* js) {
ObjectGraph graph(thread);
Array& path = Array::Handle(Array::New(limit * 2));
auto result = graph.RetainingPath(obj, path);
intptr_t length = result.length;
JSONObject jsobj(js);
jsobj.AddProperty("type", "RetainingPath");
jsobj.AddProperty("length", length);
jsobj.AddProperty("gcRootType", result.gc_root_type);
JSONArray elements(&jsobj, "elements");
Object& element = Object::Handle();
Smi& slot_offset = Smi::Handle();
Array& field_names = Array::Handle();
String& name = String::Handle();
Class& element_class = Class::Handle();
Array& element_field_map = Array::Handle();
Map& map = Map::Handle();
Array& map_data = Array::Handle();
Field& field = Field::Handle();
WeakProperty& wp = WeakProperty::Handle();
limit = Utils::Minimum(limit, length);
OffsetsTable offsets_table(thread->zone());
for (intptr_t i = 0; i < limit; ++i) {
JSONObject jselement(&elements);
element = path.At(i * 2);
jselement.AddProperty("value", element);
// Interpret the word offset from parent as list index, map key,
// weak property, or instance field.
if (i > 0) {
slot_offset ^= path.At((i * 2) - 1);
if (element.IsArray() || element.IsGrowableObjectArray()) {
intptr_t element_index =
(slot_offset.Value() - Array::element_offset(0)) /
Array::kBytesPerElement;
jselement.AddProperty("parentListIndex", element_index);
jselement.AddProperty("parentField", element_index);
} else if (element.IsRecord()) {
AddParentFieldToResponseBasedOnRecord(thread, &field_names, &name,
jselement, Record::Cast(element),
slot_offset.Value());
} else if (element.IsMap()) {
map = static_cast<MapPtr>(path.At(i * 2));
map_data = map.data();
intptr_t element_index =
(slot_offset.Value() - Array::element_offset(0)) /
Array::kBytesPerElement;
Map::Iterator iterator(map);
while (iterator.MoveNext()) {
if (iterator.CurrentKey() == map_data.At(element_index) ||
iterator.CurrentValue() == map_data.At(element_index)) {
element = iterator.CurrentKey();
jselement.AddProperty("parentMapKey", element);
break;
}
}
} else if (element.IsWeakProperty()) {
wp ^= static_cast<WeakPropertyPtr>(element.ptr());
element = wp.key();
jselement.AddProperty("parentMapKey", element);
} else {
if (element.IsInstance()) {
element_class = element.clazz();
element_field_map = element_class.OffsetToFieldMap();
intptr_t index = slot_offset.Value() >> kCompressedWordSizeLog2;
if ((index > 0) && (index < element_field_map.Length())) {
field ^= element_field_map.At(index);
if (!field.IsNull()) {
name ^= field.name();
jselement.AddProperty("parentField", name.ToCString());
continue;
}
}
}
const char* field_name = offsets_table.FieldNameForOffset(
element.GetClassId(), slot_offset.Value());
if (field_name != nullptr) {
jselement.AddProperty("parentField", field_name);
} else if (element.IsContext()) {
intptr_t element_index =
(slot_offset.Value() - Context::variable_offset(0)) /
Context::kBytesPerElement;
jselement.AddProperty("parentListIndex", element_index);
jselement.AddProperty("parentField", element_index);
} else {
jselement.AddProperty("_parentWordOffset", slot_offset.Value());
}
}
}
}
// We nil out the array after generating the response to prevent
// reporting spurious references when looking for inbound references
// after looking for a retaining path.
for (intptr_t i = 0; i < path.Length(); i++) {
path.SetAt(i, Object::null_object());
}
}
static const MethodParameter* const get_retaining_path_params[] = {
RUNNABLE_ISOLATE_PARAMETER,
nullptr,
};
static void GetRetainingPath(Thread* thread, JSONStream* js) {
const char* target_id = js->LookupParam("targetId");
if (target_id == nullptr) {
PrintMissingParamError(js, "targetId");
return;
}
const char* limit_cstr = js->LookupParam("limit");
if (limit_cstr == nullptr) {
PrintMissingParamError(js, "limit");
return;
}
intptr_t limit;
if (!GetIntegerId(limit_cstr, &limit)) {
PrintInvalidParamError(js, "limit");
return;
}
Object& obj = Object::Handle(thread->zone());
ObjectIdRing::LookupResult lookup_result;
{
HANDLESCOPE(thread);
obj = LookupHeapObject(thread, target_id, &lookup_result);
}
if (obj.ptr() == Object::sentinel().ptr()) {
if (lookup_result == ObjectIdRing::kCollected) {
PrintSentinel(js, kCollectedSentinel);
} else if (lookup_result == ObjectIdRing::kExpired) {
PrintSentinel(js, kExpiredSentinel);
} else {
PrintInvalidParamError(js, "targetId");
}
return;
}
PrintRetainingPath(thread, &obj, limit, js);
}
static const MethodParameter* const get_retained_size_params[] = {
RUNNABLE_ISOLATE_PARAMETER,
new IdParameter("targetId", true),
nullptr,
};
static void GetRetainedSize(Thread* thread, JSONStream* js) {
const char* target_id = js->LookupParam("targetId");
ASSERT(target_id != nullptr);
ObjectIdRing::LookupResult lookup_result;
Object& obj =
Object::Handle(LookupHeapObject(thread, target_id, &lookup_result));
if (obj.ptr() == Object::sentinel().ptr()) {
if (lookup_result == ObjectIdRing::kCollected) {
PrintSentinel(js, kCollectedSentinel);
} else if (lookup_result == ObjectIdRing::kExpired) {
PrintSentinel(js, kExpiredSentinel);
} else {
PrintInvalidParamError(js, "targetId");
}
return;
}
// TODO(rmacnak): There is no way to get the size retained by a class object.
// SizeRetainedByClass should be a separate RPC.
if (obj.IsClass()) {
const Class& cls = Class::Cast(obj);
ObjectGraph graph(thread);
intptr_t retained_size = graph.SizeRetainedByClass(cls.id());
const Object& result = Object::Handle(Integer::New(retained_size));
result.PrintJSON(js, true);
return;
}
ObjectGraph graph(thread);
intptr_t retained_size = graph.SizeRetainedByInstance(obj);
const Object& result = Object::Handle(Integer::New(retained_size));
result.PrintJSON(js, true);
}
static const MethodParameter* const get_reachable_size_params[] = {
RUNNABLE_ISOLATE_PARAMETER,
new IdParameter("targetId", true),
nullptr,
};
static void GetReachableSize(Thread* thread, JSONStream* js) {
const char* target_id = js->LookupParam("targetId");
ASSERT(target_id != nullptr);
ObjectIdRing::LookupResult lookup_result;
Object& obj =
Object::Handle(LookupHeapObject(thread, target_id, &lookup_result));
if (obj.ptr() == Object::sentinel().ptr()) {
if (lookup_result == ObjectIdRing::kCollected) {
PrintSentinel(js, kCollectedSentinel);
} else if (lookup_result == ObjectIdRing::kExpired) {
PrintSentinel(js, kExpiredSentinel);
} else {
PrintInvalidParamError(js, "targetId");