1// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2// for details. All rights reserved. Use of this source code is governed by a
3// BSD-style license that can be found in the LICENSE file.
4
5#include "vm/service.h"
6
7#include <memory>
8#include <utility>
9
10#include "include/dart_api.h"
11#include "include/dart_native_api.h"
12#include "platform/globals.h"
13
14#include "platform/unicode.h"
15#include "platform/utils.h"
16#include "vm/base64.h"
17#include "vm/canonical_tables.h"
18#include "vm/closure_functions_cache.h"
19#include "vm/compiler/jit/compiler.h"
20#include "vm/cpu.h"
21#include "vm/dart_api_impl.h"
22#include "vm/dart_api_message.h"
23#include "vm/dart_api_state.h"
24#include "vm/dart_entry.h"
25#include "vm/debugger.h"
26#include "vm/heap/safepoint.h"
27#include "vm/isolate.h"
28#include "vm/kernel_isolate.h"
29#include "vm/lockers.h"
30#include "vm/message.h"
31#include "vm/message_handler.h"
32#include "vm/message_snapshot.h"
33#include "vm/native_arguments.h"
34#include "vm/native_entry.h"
35#include "vm/native_symbol.h"
36#include "vm/object.h"
37#include "vm/object_graph.h"
38#include "vm/object_id_ring.h"
39#include "vm/object_store.h"
40#include "vm/parser.h"
41#include "vm/port.h"
42#include "vm/profiler.h"
43#include "vm/profiler_service.h"
44#include "vm/raw_object_fields.h"
45#include "vm/resolver.h"
46#include "vm/reusable_handles.h"
47#include "vm/service_event.h"
48#include "vm/service_isolate.h"
49#include "vm/source_report.h"
50#include "vm/stack_frame.h"
51#include "vm/symbols.h"
52#include "vm/timeline.h"
53#include "vm/version.h"
54
55#if defined(SUPPORT_PERFETTO)
56#include "vm/perfetto_utils.h"
57#endif // defined(SUPPORT_PERFETTO)
58
59namespace dart {
60
61#define Z (T->zone())
62
63DECLARE_FLAG(bool, trace_service);
64DECLARE_FLAG(bool, trace_service_pause_events);
65DECLARE_FLAG(bool, profile_vm);
66DEFINE_FLAG(charp,
67 vm_name,
68 "vm",
69 "The default name of this vm as reported by the VM service "
70 "protocol");
71
72DEFINE_FLAG(bool,
73 warn_on_pause_with_no_debugger,
74 false,
75 "Print a message when an isolate is paused but there is no "
76 "debugger attached.");
77
78DEFINE_FLAG(
79 charp,
80 log_service_response_sizes,
81 nullptr,
82 "Log sizes of service responses and events to a file in CSV format.");
83
84void* Service::service_response_size_log_file_ = nullptr;
85
86void Service::LogResponseSize(const char* method, JSONStream* js) {
87 if (service_response_size_log_file_ == nullptr) {
88 return;
89 }
90 Dart_FileWriteCallback file_write = Dart::file_write_callback();
91 char* entry =
92 OS::SCreate(nullptr, "%s, %" Pd "\n", method, js->buffer()->length());
93 (*file_write)(entry, strlen(entry), service_response_size_log_file_);
94 free(entry);
95}
96
97void Service::Init() {
98 if (FLAG_log_service_response_sizes == nullptr) {
99 return;
100 }
101 Dart_FileOpenCallback file_open = Dart::file_open_callback();
102 Dart_FileWriteCallback file_write = Dart::file_write_callback();
103 Dart_FileCloseCallback file_close = Dart::file_close_callback();
104 if ((file_open == nullptr) || (file_write == nullptr) ||
105 (file_close == nullptr)) {
106 OS::PrintErr(format: "Error: Could not access file callbacks.");
107 UNREACHABLE();
108 }
109 ASSERT(service_response_size_log_file_ == nullptr);
110 service_response_size_log_file_ =
111 (*file_open)(FLAG_log_service_response_sizes, true);
112 if (service_response_size_log_file_ == nullptr) {
113 OS::PrintErr(format: "Warning: Failed to open service response size log file: %s\n",
114 FLAG_log_service_response_sizes);
115 return;
116 }
117}
118
119void Service::Cleanup() {
120 if (service_response_size_log_file_ == nullptr) {
121 return;
122 }
123 Dart_FileCloseCallback file_close = Dart::file_close_callback();
124 (*file_close)(service_response_size_log_file_);
125 service_response_size_log_file_ = nullptr;
126}
127
128static void PrintInvalidParamError(JSONStream* js, const char* param) {
129#if !defined(PRODUCT)
130 js->PrintError(code: kInvalidParams, details_format: "%s: invalid '%s' parameter: %s", js->method(),
131 param, js->LookupParam(key: param));
132#endif
133}
134
135// TODO(johnmccutchan): Split into separate file and write unit tests.
136class MethodParameter {
137 public:
138 MethodParameter(const char* name, bool required)
139 : name_(name), required_(required) {}
140
141 virtual ~MethodParameter() {}
142
143 virtual bool Validate(const char* value) const { return true; }
144
145 virtual bool ValidateObject(const Object& value) const { return true; }
146
147 const char* name() const { return name_; }
148
149 bool required() const { return required_; }
150
151 virtual void PrintError(const char* name,
152 const char* value,
153 JSONStream* js) const {
154 PrintInvalidParamError(js, param: name);
155 }
156
157 virtual void PrintErrorObject(const char* name,
158 const Object& value,
159 JSONStream* js) const {
160 PrintInvalidParamError(js, param: name);
161 }
162
163 private:
164 const char* name_;
165 bool required_;
166};
167
168class NoSuchParameter : public MethodParameter {
169 public:
170 explicit NoSuchParameter(const char* name) : MethodParameter(name, false) {}
171
172 virtual bool Validate(const char* value) const { return (value == nullptr); }
173
174 virtual bool ValidateObject(const Object& value) const {
175 return value.IsNull();
176 }
177};
178
179#define ISOLATE_PARAMETER new IdParameter("isolateId", true)
180#define ISOLATE_GROUP_PARAMETER new IdParameter("isolateGroupId", true)
181#define NO_ISOLATE_PARAMETER new NoSuchParameter("isolateId")
182#define RUNNABLE_ISOLATE_PARAMETER new RunnableIsolateParameter("isolateId")
183#define OBJECT_PARAMETER new IdParameter("objectId", true)
184
185class EnumListParameter : public MethodParameter {
186 public:
187 EnumListParameter(const char* name, bool required, const char* const* enums)
188 : MethodParameter(name, required), enums_(enums) {}
189
190 virtual bool Validate(const char* value) const {
191 return ElementCount(value) >= 0;
192 }
193
194 const char** Parse(char* value) const {
195 const char* kJsonChars = " \t\r\n[,]";
196
197 // Make a writeable copy of the value.
198 intptr_t element_count = ElementCount(value);
199 if (element_count < 0) {
200 return nullptr;
201 }
202 intptr_t element_pos = 0;
203
204 // Allocate our element array. +1 for nullptr terminator.
205 // The caller is responsible for deleting this memory.
206 char** elements = new char*[element_count + 1];
207 elements[element_count] = nullptr;
208
209 // Parse the string destructively. Build the list of elements.
210 while (element_pos < element_count) {
211 // Skip to the next element.
212 value += strspn(value, kJsonChars);
213
214 intptr_t len = strcspn(value, kJsonChars);
215 ASSERT(len > 0); // We rely on the parameter being validated already.
216 value[len] = '\0';
217 elements[element_pos++] = value;
218
219 // Advance. +1 for null terminator.
220 value += (len + 1);
221 }
222 return const_cast<const char**>(elements);
223 }
224
225 private:
226 // For now observatory enums are ascii letters plus underscore.
227 static bool IsEnumChar(char c) {
228 return (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) ||
229 (c == '_'));
230 }
231
232 // Returns number of elements in the list. -1 on parse error.
233 intptr_t ElementCount(const char* value) const {
234 const char* kJsonWhitespaceChars = " \t\r\n";
235 if (value == nullptr) {
236 return -1;
237 }
238 const char* cp = value;
239 cp += strspn(cp, kJsonWhitespaceChars);
240 if (*cp++ != '[') {
241 // Missing initial [.
242 return -1;
243 }
244 bool closed = false;
245 bool element_allowed = true;
246 intptr_t element_count = 0;
247 while (true) {
248 // Skip json whitespace.
249 cp += strspn(cp, kJsonWhitespaceChars);
250 switch (*cp) {
251 case '\0':
252 return closed ? element_count : -1;
253 case ']':
254 closed = true;
255 cp++;
256 break;
257 case ',':
258 if (element_allowed) {
259 return -1;
260 }
261 element_allowed = true;
262 cp++;
263 break;
264 default:
265 if (!element_allowed) {
266 return -1;
267 }
268 bool valid_enum = false;
269 const char* id_start = cp;
270 while (IsEnumChar(c: *cp)) {
271 cp++;
272 }
273 if (cp == id_start) {
274 // Empty identifier, something like this [,].
275 return -1;
276 }
277 intptr_t id_len = cp - id_start;
278 if (enums_ != nullptr) {
279 for (intptr_t i = 0; enums_[i] != nullptr; i++) {
280 intptr_t len = strlen(enums_[i]);
281 if (len == id_len && strncmp(id_start, enums_[i], len) == 0) {
282 element_count++;
283 valid_enum = true;
284 element_allowed = false; // we need a comma first.
285 break;
286 }
287 }
288 }
289 if (!valid_enum) {
290 return -1;
291 }
292 break;
293 }
294 }
295 }
296
297 const char* const* enums_;
298};
299
300#if defined(SUPPORT_TIMELINE)
301static const char* const timeline_streams_enum_names[] = {
302 "all",
303#define DEFINE_NAME(name, ...) #name,
304 TIMELINE_STREAM_LIST(DEFINE_NAME)
305#undef DEFINE_NAME
306 nullptr};
307
308static const MethodParameter* const set_vm_timeline_flags_params[] = {
309 NO_ISOLATE_PARAMETER,
310 new EnumListParameter("recordedStreams",
311 false,
312 timeline_streams_enum_names),
313 nullptr,
314};
315
316static bool HasStream(const char** recorded_streams, const char* stream) {
317 while (*recorded_streams != nullptr) {
318 if ((strstr(*recorded_streams, "all") != nullptr) ||
319 (strstr(*recorded_streams, stream) != nullptr)) {
320 return true;
321 }
322 recorded_streams++;
323 }
324 return false;
325}
326
327bool Service::EnableTimelineStreams(char* categories_list) {
328 const EnumListParameter* recorded_streams_param =
329 static_cast<const EnumListParameter*>(set_vm_timeline_flags_params[1]);
330 const char** streams = recorded_streams_param->Parse(value: categories_list);
331 if (streams == nullptr) {
332 return false;
333 }
334
335#define SET_ENABLE_STREAM(name, ...) \
336 Timeline::SetStream##name##Enabled(HasStream(streams, #name));
337 TIMELINE_STREAM_LIST(SET_ENABLE_STREAM);
338#undef SET_ENABLE_STREAM
339
340 delete[] streams;
341
342#if !defined(PRODUCT)
343 // Notify clients that the set of subscribed streams has been updated.
344 if (Service::timeline_stream.enabled()) {
345 ServiceEvent event(ServiceEvent::kTimelineStreamSubscriptionsUpdate);
346 Service::HandleEvent(event: &event);
347 }
348#endif
349
350 return true;
351}
352#endif // defined(SUPPORT_TIMELINE)
353
354#ifndef PRODUCT
355// The name of this of this vm as reported by the VM service protocol.
356static char* vm_name = nullptr;
357
358static const char* GetVMName() {
359 if (vm_name == nullptr) {
360 return FLAG_vm_name;
361 }
362 return vm_name;
363}
364
365ServiceIdZone::ServiceIdZone() {}
366
367ServiceIdZone::~ServiceIdZone() {}
368
369RingServiceIdZone::RingServiceIdZone()
370 : ring_(nullptr), policy_(ObjectIdRing::kAllocateId) {}
371
372RingServiceIdZone::~RingServiceIdZone() {}
373
374void RingServiceIdZone::Init(ObjectIdRing* ring,
375 ObjectIdRing::IdPolicy policy) {
376 ring_ = ring;
377 policy_ = policy;
378}
379
380char* RingServiceIdZone::GetServiceId(const Object& obj) {
381 ASSERT(ring_ != nullptr);
382 Thread* thread = Thread::Current();
383 Zone* zone = thread->zone();
384 ASSERT(zone != nullptr);
385 const intptr_t id = ring_->GetIdForObject(raw_obj: obj.ptr(), policy: policy_);
386 return zone->PrintToString("objects/%" Pd "", id);
387}
388
389// TODO(johnmccutchan): Unify embedder service handler lists and their APIs.
390EmbedderServiceHandler* Service::isolate_service_handler_head_ = nullptr;
391EmbedderServiceHandler* Service::root_service_handler_head_ = nullptr;
392struct ServiceMethodDescriptor;
393const ServiceMethodDescriptor* FindMethod(const char* method_name);
394
395// Support for streams defined in embedders.
396Dart_ServiceStreamListenCallback Service::stream_listen_callback_ = nullptr;
397Dart_ServiceStreamCancelCallback Service::stream_cancel_callback_ = nullptr;
398Dart_GetVMServiceAssetsArchive Service::get_service_assets_callback_ = nullptr;
399Dart_EmbedderInformationCallback Service::embedder_information_callback_ =
400 nullptr;
401
402// These are the set of streams known to the core VM.
403StreamInfo Service::vm_stream("VM");
404StreamInfo Service::isolate_stream("Isolate");
405StreamInfo Service::debug_stream("Debug");
406StreamInfo Service::gc_stream("GC");
407StreamInfo Service::echo_stream("_Echo");
408StreamInfo Service::heapsnapshot_stream("HeapSnapshot");
409StreamInfo Service::logging_stream("Logging");
410StreamInfo Service::extension_stream("Extension");
411StreamInfo Service::timeline_stream("Timeline");
412StreamInfo Service::profiler_stream("Profiler");
413
414const uint8_t* Service::dart_library_kernel_ = nullptr;
415intptr_t Service::dart_library_kernel_len_ = 0;
416
417// Keep streams_ in sync with the protected streams in
418// lib/developer/extension.dart
419static StreamInfo* const streams_[] = {
420 &Service::vm_stream, &Service::isolate_stream,
421 &Service::debug_stream, &Service::gc_stream,
422 &Service::echo_stream, &Service::heapsnapshot_stream,
423 &Service::logging_stream, &Service::extension_stream,
424 &Service::timeline_stream, &Service::profiler_stream,
425};
426
427bool Service::ListenStream(const char* stream_id,
428 bool include_private_members) {
429 if (FLAG_trace_service) {
430 OS::PrintErr(format: "vm-service: starting stream '%s'\n", stream_id);
431 }
432 intptr_t num_streams = sizeof(streams_) / sizeof(streams_[0]);
433 for (intptr_t i = 0; i < num_streams; i++) {
434 if (strcmp(stream_id, streams_[i]->id()) == 0) {
435 streams_[i]->set_enabled(true);
436 streams_[i]->set_include_private_members(include_private_members);
437 return true;
438 }
439 }
440 if (stream_listen_callback_ != nullptr) {
441 Thread* T = Thread::Current();
442 TransitionVMToNative transition(T);
443 return (*stream_listen_callback_)(stream_id);
444 }
445 return false;
446}
447
448void Service::CancelStream(const char* stream_id) {
449 if (FLAG_trace_service) {
450 OS::PrintErr(format: "vm-service: stopping stream '%s'\n", stream_id);
451 }
452 intptr_t num_streams = sizeof(streams_) / sizeof(streams_[0]);
453 for (intptr_t i = 0; i < num_streams; i++) {
454 if (strcmp(stream_id, streams_[i]->id()) == 0) {
455 streams_[i]->set_enabled(false);
456 return;
457 }
458 }
459 if (stream_cancel_callback_ != nullptr) {
460 Thread* T = Thread::Current();
461 TransitionVMToNative transition(T);
462 return (*stream_cancel_callback_)(stream_id);
463 }
464}
465
466ObjectPtr Service::RequestAssets() {
467 Thread* T = Thread::Current();
468 Object& object = Object::Handle();
469 {
470 Api::Scope api_scope(T);
471 Dart_Handle handle;
472 {
473 TransitionVMToNative transition(T);
474 if (get_service_assets_callback_ == nullptr) {
475 return Object::null();
476 }
477 handle = get_service_assets_callback_();
478 if (Dart_IsError(handle)) {
479 Dart_PropagateError(handle);
480 }
481 }
482 object = Api::UnwrapHandle(object: handle);
483 }
484 if (object.IsNull()) {
485 return Object::null();
486 }
487 if (!object.IsTypedData()) {
488 const String& error_message = String::Handle(
489 ptr: String::New(cstr: "An implementation of Dart_GetVMServiceAssetsArchive "
490 "should return a Uint8Array or null."));
491 const Error& error = Error::Handle(ptr: ApiError::New(message: error_message));
492 Exceptions::PropagateError(error);
493 return Object::null();
494 }
495 const TypedData& typed_data = TypedData::Cast(obj: object);
496 if (typed_data.ElementSizeInBytes() != 1) {
497 const String& error_message = String::Handle(
498 ptr: String::New(cstr: "An implementation of Dart_GetVMServiceAssetsArchive "
499 "should return a Uint8Array or null."));
500 const Error& error = Error::Handle(ptr: ApiError::New(message: error_message));
501 Exceptions::PropagateError(error);
502 return Object::null();
503 }
504 return object.ptr();
505}
506
507static void PrintSuccess(JSONStream* js) {
508 JSONObject jsobj(js);
509 jsobj.AddProperty(name: "type", s: "Success");
510}
511
512static bool CheckDebuggerDisabled(Thread* thread, JSONStream* js) {
513#if defined(DART_PRECOMPILED_RUNTIME)
514 js->PrintError(kFeatureDisabled, "Debugger is disabled in AOT mode.");
515 return true;
516#else
517 if (thread->isolate()->debugger() == nullptr) {
518 js->PrintError(code: kFeatureDisabled, details_format: "Debugger is disabled.");
519 return true;
520 }
521 return false;
522#endif
523}
524
525static bool CheckCompilerDisabled(Thread* thread, JSONStream* js) {
526#if defined(DART_PRECOMPILED_RUNTIME)
527 js->PrintError(kFeatureDisabled, "Compiler is disabled in AOT mode.");
528 return true;
529#else
530 return false;
531#endif
532}
533
534static bool CheckProfilerDisabled(Thread* thread, JSONStream* js) {
535 if (!FLAG_profiler) {
536 js->PrintError(code: kFeatureDisabled, details_format: "Profiler is disabled.");
537 return true;
538 }
539 return false;
540}
541
542static bool GetIntegerId(const char* s, intptr_t* id, int base = 10) {
543 if ((s == nullptr) || (*s == '\0')) {
544 // Empty string.
545 return false;
546 }
547 if (id == nullptr) {
548 // No id pointer.
549 return false;
550 }
551 intptr_t r = 0;
552 char* end_ptr = nullptr;
553#if defined(ARCH_IS_32_BIT)
554 r = strtol(s, &end_ptr, base);
555#else
556 r = strtoll(s, &end_ptr, base);
557#endif
558 if (end_ptr == s) {
559 // String was not advanced at all, cannot be valid.
560 return false;
561 }
562 *id = r;
563 return true;
564}
565
566static bool GetUnsignedIntegerId(const char* s, uintptr_t* id, int base = 10) {
567 if ((s == nullptr) || (*s == '\0')) {
568 // Empty string.
569 return false;
570 }
571 if (id == nullptr) {
572 // No id pointer.
573 return false;
574 }
575 uintptr_t r = 0;
576 char* end_ptr = nullptr;
577#if defined(ARCH_IS_32_BIT)
578 r = strtoul(s, &end_ptr, base);
579#else
580 r = strtoull(s, &end_ptr, base);
581#endif
582 if (end_ptr == s) {
583 // String was not advanced at all, cannot be valid.
584 return false;
585 }
586 *id = r;
587 return true;
588}
589
590static bool GetInteger64Id(const char* s, int64_t* id, int base = 10) {
591 if ((s == nullptr) || (*s == '\0')) {
592 // Empty string.
593 return false;
594 }
595 if (id == nullptr) {
596 // No id pointer.
597 return false;
598 }
599 int64_t r = 0;
600 char* end_ptr = nullptr;
601 r = strtoll(s, &end_ptr, base);
602 if (end_ptr == s) {
603 // String was not advanced at all, cannot be valid.
604 return false;
605 }
606 *id = r;
607 return true;
608}
609
610// Scans the string until the '-' character. Returns pointer to string
611// at '-' character. Returns nullptr if not found.
612static const char* ScanUntilDash(const char* s) {
613 if ((s == nullptr) || (*s == '\0')) {
614 // Empty string.
615 return nullptr;
616 }
617 while (*s != '\0') {
618 if (*s == '-') {
619 return s;
620 }
621 s++;
622 }
623 return nullptr;
624}
625
626static bool GetCodeId(const char* s, int64_t* timestamp, uword* address) {
627 if ((s == nullptr) || (*s == '\0')) {
628 // Empty string.
629 return false;
630 }
631 if ((timestamp == nullptr) || (address == nullptr)) {
632 // Bad arguments.
633 return false;
634 }
635 // Extract the timestamp.
636 if (!GetInteger64Id(s, id: timestamp, base: 16) || (*timestamp < 0)) {
637 return false;
638 }
639 s = ScanUntilDash(s);
640 if (s == nullptr) {
641 return false;
642 }
643 // Skip the dash.
644 s++;
645 // Extract the PC.
646 if (!GetUnsignedIntegerId(s, id: address, base: 16)) {
647 return false;
648 }
649 return true;
650}
651
652// Verifies that |s| begins with |prefix| and then calls |GetIntegerId| on
653// the remainder of |s|.
654static bool GetPrefixedIntegerId(const char* s,
655 const char* prefix,
656 intptr_t* service_id) {
657 if (s == nullptr) {
658 return false;
659 }
660 ASSERT(prefix != nullptr);
661 const intptr_t kInputLen = strlen(s);
662 const intptr_t kPrefixLen = strlen(prefix);
663 ASSERT(kPrefixLen > 0);
664 if (kInputLen <= kPrefixLen) {
665 return false;
666 }
667 if (strncmp(s, prefix, kPrefixLen) != 0) {
668 return false;
669 }
670 // Prefix satisfied. Move forward.
671 s += kPrefixLen;
672 // Attempt to read integer id.
673 return GetIntegerId(s, id: service_id);
674}
675
676static bool IsValidClassId(Isolate* isolate, intptr_t cid) {
677 ASSERT(isolate != nullptr);
678 ClassTable* class_table = isolate->group()->class_table();
679 ASSERT(class_table != nullptr);
680 return class_table->IsValidIndex(cid) && class_table->HasValidClassAt(cid);
681}
682
683static ClassPtr GetClassForId(Isolate* isolate, intptr_t cid) {
684 ASSERT(isolate == Isolate::Current());
685 ASSERT(isolate != nullptr);
686 ClassTable* class_table = isolate->group()->class_table();
687 ASSERT(class_table != nullptr);
688 return class_table->At(cid);
689}
690
691class DartStringParameter : public MethodParameter {
692 public:
693 DartStringParameter(const char* name, bool required)
694 : MethodParameter(name, required) {}
695
696 virtual bool ValidateObject(const Object& value) const {
697 return value.IsString();
698 }
699};
700
701class DartListParameter : public MethodParameter {
702 public:
703 DartListParameter(const char* name, bool required)
704 : MethodParameter(name, required) {}
705
706 virtual bool ValidateObject(const Object& value) const {
707 return value.IsArray() || value.IsGrowableObjectArray();
708 }
709};
710
711class BoolParameter : public MethodParameter {
712 public:
713 BoolParameter(const char* name, bool required)
714 : MethodParameter(name, required) {}
715
716 virtual bool Validate(const char* value) const {
717 if (value == nullptr) {
718 return false;
719 }
720 return (strcmp("true", value) == 0) || (strcmp("false", value) == 0);
721 }
722
723 static bool Parse(const char* value, bool default_value = false) {
724 if (value == nullptr) {
725 return default_value;
726 }
727 return strcmp("true", value) == 0;
728 }
729};
730
731class UIntParameter : public MethodParameter {
732 public:
733 UIntParameter(const char* name, bool required)
734 : MethodParameter(name, required) {}
735
736 virtual bool Validate(const char* value) const {
737 if (value == nullptr) {
738 return false;
739 }
740 for (const char* cp = value; *cp != '\0'; cp++) {
741 if (*cp < '0' || *cp > '9') {
742 return false;
743 }
744 }
745 return true;
746 }
747
748 static uintptr_t Parse(const char* value) {
749 if (value == nullptr) {
750 return -1;
751 }
752 char* end_ptr = nullptr;
753 uintptr_t result = strtoul(value, &end_ptr, 10);
754 ASSERT(*end_ptr == '\0'); // Parsed full string
755 return result;
756 }
757};
758
759class Int64Parameter : public MethodParameter {
760 public:
761 Int64Parameter(const char* name, bool required)
762 : MethodParameter(name, required) {}
763
764 virtual bool Validate(const char* value) const {
765 if (value == nullptr) {
766 return false;
767 }
768 for (const char* cp = value; *cp != '\0'; cp++) {
769 if ((*cp < '0' || *cp > '9') && (*cp != '-')) {
770 return false;
771 }
772 }
773 return true;
774 }
775
776 static int64_t Parse(const char* value, int64_t default_value = -1) {
777 if ((value == nullptr) || (*value == '\0')) {
778 return default_value;
779 }
780 char* end_ptr = nullptr;
781 int64_t result = strtoll(value, &end_ptr, 10);
782 ASSERT(*end_ptr == '\0'); // Parsed full string
783 return result;
784 }
785};
786
787class UInt64Parameter : public MethodParameter {
788 public:
789 UInt64Parameter(const char* name, bool required)
790 : MethodParameter(name, required) {}
791
792 virtual bool Validate(const char* value) const {
793 if (value == nullptr) {
794 return false;
795 }
796 for (const char* cp = value; *cp != '\0'; cp++) {
797 if ((*cp < '0' || *cp > '9') && (*cp != '-')) {
798 return false;
799 }
800 }
801 return true;
802 }
803
804 static uint64_t Parse(const char* value, uint64_t default_value = 0) {
805 if ((value == nullptr) || (*value == '\0')) {
806 return default_value;
807 }
808 char* end_ptr = nullptr;
809 uint64_t result = strtoull(value, &end_ptr, 10);
810 ASSERT(*end_ptr == '\0'); // Parsed full string
811 return result;
812 }
813};
814
815class IdParameter : public MethodParameter {
816 public:
817 IdParameter(const char* name, bool required)
818 : MethodParameter(name, required) {}
819
820 virtual bool Validate(const char* value) const { return (value != nullptr); }
821};
822
823class StringParameter : public MethodParameter {
824 public:
825 StringParameter(const char* name, bool required)
826 : MethodParameter(name, required) {}
827
828 virtual bool Validate(const char* value) const { return (value != nullptr); }
829};
830
831class RunnableIsolateParameter : public MethodParameter {
832 public:
833 explicit RunnableIsolateParameter(const char* name)
834 : MethodParameter(name, true) {}
835
836 virtual bool Validate(const char* value) const {
837 Isolate* isolate = Isolate::Current();
838 return (value != nullptr) && (isolate != nullptr) &&
839 (isolate->is_runnable());
840 }
841
842 virtual void PrintError(const char* name,
843 const char* value,
844 JSONStream* js) const {
845 js->PrintError(code: kIsolateMustBeRunnable,
846 details_format: "Isolate must be runnable before this request is made.");
847 }
848};
849
850class EnumParameter : public MethodParameter {
851 public:
852 EnumParameter(const char* name, bool required, const char* const* enums)
853 : MethodParameter(name, required), enums_(enums) {}
854
855 virtual bool Validate(const char* value) const {
856 if (value == nullptr) {
857 return true;
858 }
859 for (intptr_t i = 0; enums_[i] != nullptr; i++) {
860 if (strcmp(value, enums_[i]) == 0) {
861 return true;
862 }
863 }
864 return false;
865 }
866
867 private:
868 const char* const* enums_;
869};
870
871// If the key is not found, this function returns the last element in the
872// values array. This can be used to encode the default value.
873template <typename T>
874T EnumMapper(const char* value, const char* const* enums, T* values) {
875 ASSERT(value != nullptr);
876 intptr_t i = 0;
877 for (i = 0; enums[i] != nullptr; i++) {
878 if (strcmp(value, enums[i]) == 0) {
879 return values[i];
880 }
881 }
882 // Default value.
883 return values[i];
884}
885
886typedef void (*ServiceMethodEntry)(Thread* thread, JSONStream* js);
887
888struct ServiceMethodDescriptor {
889 const char* name;
890 const ServiceMethodEntry entry;
891 const MethodParameter* const* parameters;
892};
893
894static void PrintMissingParamError(JSONStream* js, const char* param) {
895 js->PrintError(code: kInvalidParams, details_format: "%s expects the '%s' parameter", js->method(),
896 param);
897}
898
899static void PrintUnrecognizedMethodError(JSONStream* js) {
900 js->PrintError(code: kMethodNotFound, details_format: nullptr);
901}
902
903// TODO(johnmccutchan): Do we reject unexpected parameters?
904static bool ValidateParameters(const MethodParameter* const* parameters,
905 JSONStream* js) {
906 if (parameters == nullptr) {
907 return true;
908 }
909 if (js->NumObjectParameters() > 0) {
910 Object& value = Object::Handle();
911 for (intptr_t i = 0; parameters[i] != nullptr; i++) {
912 const MethodParameter* parameter = parameters[i];
913 const char* name = parameter->name();
914 const bool required = parameter->required();
915 value = js->LookupObjectParam(key: name);
916 const bool has_parameter = !value.IsNull();
917 if (required && !has_parameter) {
918 PrintMissingParamError(js, param: name);
919 return false;
920 }
921 if (has_parameter && !parameter->ValidateObject(value)) {
922 parameter->PrintErrorObject(name, value, js);
923 return false;
924 }
925 }
926 } else {
927 for (intptr_t i = 0; parameters[i] != nullptr; i++) {
928 const MethodParameter* parameter = parameters[i];
929 const char* name = parameter->name();
930 const bool required = parameter->required();
931 const char* value = js->LookupParam(key: name);
932 const bool has_parameter = (value != nullptr);
933 if (required && !has_parameter) {
934 PrintMissingParamError(js, param: name);
935 return false;
936 }
937 if (has_parameter && !parameter->Validate(value)) {
938 parameter->PrintError(name, value, js);
939 return false;
940 }
941 }
942 }
943 return true;
944}
945
946void Service::PostError(const String& method_name,
947 const Array& parameter_keys,
948 const Array& parameter_values,
949 const Instance& reply_port,
950 const Instance& id,
951 const Error& error) {
952 Thread* T = Thread::Current();
953 StackZone zone(T);
954 JSONStream js;
955 js.Setup(zone: zone.GetZone(), reply_port: SendPort::Cast(obj: reply_port).Id(), seq: id, method: method_name,
956 param_keys: parameter_keys, param_values: parameter_values);
957 js.PrintError(code: kExtensionError, details_format: "Error in extension `%s`: %s", js.method(),
958 error.ToErrorCString());
959 js.PostReply();
960}
961
962ErrorPtr Service::InvokeMethod(Isolate* I,
963 const Array& msg,
964 bool parameters_are_dart_objects) {
965 Thread* T = Thread::Current();
966 ASSERT(I == T->isolate());
967 ASSERT(I != nullptr);
968 ASSERT(T->execution_state() == Thread::kThreadInVM);
969 ASSERT(!msg.IsNull());
970 ASSERT(msg.Length() == 6);
971
972 {
973 StackZone zone(T);
974
975 Instance& reply_port = Instance::Handle(Z);
976 Instance& seq = String::Handle(Z);
977 String& method_name = String::Handle(Z);
978 Array& param_keys = Array::Handle(Z);
979 Array& param_values = Array::Handle(Z);
980 reply_port ^= msg.At(1);
981 seq ^= msg.At(2);
982 method_name ^= msg.At(3);
983 param_keys ^= msg.At(4);
984 param_values ^= msg.At(5);
985
986 ASSERT(!method_name.IsNull());
987 ASSERT(seq.IsNull() || seq.IsString() || seq.IsNumber());
988 ASSERT(!param_keys.IsNull());
989 ASSERT(!param_values.IsNull());
990 ASSERT(param_keys.Length() == param_values.Length());
991
992 // We expect a reply port unless there is a null sequence id,
993 // which indicates that no reply should be sent. We use this in
994 // tests.
995 if (!seq.IsNull() && !reply_port.IsSendPort()) {
996 FATAL("SendPort expected.");
997 }
998
999 JSONStream js;
1000 Dart_Port reply_port_id =
1001 (reply_port.IsNull() ? ILLEGAL_PORT : SendPort::Cast(obj: reply_port).Id());
1002 js.Setup(zone: zone.GetZone(), reply_port: reply_port_id, seq, method: method_name, param_keys,
1003 param_values, parameters_are_dart_objects);
1004
1005 // RPC came in with a custom service id zone.
1006 const char* id_zone_param = js.LookupParam(key: "_idZone");
1007
1008 if (id_zone_param != nullptr) {
1009 // Override id zone.
1010 if (strcmp("default", id_zone_param) == 0) {
1011 // Ring with eager id allocation. This is the default ring and default
1012 // policy.
1013 // Nothing to do.
1014 } else if (strcmp("default.reuse", id_zone_param) == 0) {
1015 // Change the default ring's policy.
1016 RingServiceIdZone* zone =
1017 reinterpret_cast<RingServiceIdZone*>(js.id_zone());
1018 zone->set_policy(ObjectIdRing::kReuseId);
1019 } else {
1020 // TODO(johnmccutchan): Support creating, deleting, and selecting
1021 // custom service id zones.
1022 // For now, always return an error.
1023 PrintInvalidParamError(js: &js, param: "_idZone");
1024 js.PostReply();
1025 return T->StealStickyError();
1026 }
1027 }
1028 const char* c_method_name = method_name.ToCString();
1029
1030 const ServiceMethodDescriptor* method = FindMethod(method_name: c_method_name);
1031 if (method != nullptr) {
1032 if (!ValidateParameters(parameters: method->parameters, js: &js)) {
1033 js.PostReply();
1034 return T->StealStickyError();
1035 }
1036 method->entry(T, &js);
1037 Service::LogResponseSize(method: c_method_name, js: &js);
1038 js.PostReply();
1039 return T->StealStickyError();
1040 }
1041
1042 EmbedderServiceHandler* handler = FindIsolateEmbedderHandler(name: c_method_name);
1043 if (handler == nullptr) {
1044 handler = FindRootEmbedderHandler(name: c_method_name);
1045 }
1046
1047 if (handler != nullptr) {
1048 EmbedderHandleMessage(handler, js: &js);
1049 return T->StealStickyError();
1050 }
1051
1052 const Instance& extension_handler =
1053 Instance::Handle(Z, I->LookupServiceExtensionHandler(name: method_name));
1054 if (!extension_handler.IsNull()) {
1055 ScheduleExtensionHandler(handler: extension_handler, method_name, parameter_keys: param_keys,
1056 parameter_values: param_values, reply_port, id: seq);
1057 // Schedule was successful. Extension code will post a reply
1058 // asynchronously.
1059 return T->StealStickyError();
1060 }
1061
1062 PrintUnrecognizedMethodError(js: &js);
1063 js.PostReply();
1064 return T->StealStickyError();
1065 }
1066}
1067
1068ErrorPtr Service::HandleRootMessage(const Array& msg_instance) {
1069 Isolate* isolate = Isolate::Current();
1070 return InvokeMethod(I: isolate, msg: msg_instance);
1071}
1072
1073ErrorPtr Service::HandleObjectRootMessage(const Array& msg_instance) {
1074 Isolate* isolate = Isolate::Current();
1075 return InvokeMethod(I: isolate, msg: msg_instance, parameters_are_dart_objects: true);
1076}
1077
1078ErrorPtr Service::HandleIsolateMessage(Isolate* isolate, const Array& msg) {
1079 ASSERT(isolate != nullptr);
1080 const Error& error = Error::Handle(ptr: InvokeMethod(I: isolate, msg));
1081 return MaybePause(isolate, error);
1082}
1083
1084static void Finalizer(void* isolate_callback_data, void* buffer) {
1085 free(buffer);
1086}
1087
1088void Service::SendEvent(const char* stream_id,
1089 const char* event_type,
1090 uint8_t* bytes,
1091 intptr_t bytes_length) {
1092 Thread* thread = Thread::Current();
1093 Isolate* isolate = thread->isolate();
1094 ASSERT(isolate != nullptr);
1095
1096 if (FLAG_trace_service) {
1097 OS::PrintErr(
1098 "vm-service: Pushing ServiceEvent(isolate='%s', "
1099 "isolateId='" ISOLATE_SERVICE_ID_FORMAT_STRING
1100 "', kind='%s',"
1101 " len=%" Pd ") to stream %s\n",
1102 isolate->name(), static_cast<int64_t>(isolate->main_port()), event_type,
1103 bytes_length, stream_id);
1104 }
1105
1106 bool result;
1107 {
1108 Dart_CObject cbytes;
1109 cbytes.type = Dart_CObject_kExternalTypedData;
1110 cbytes.value.as_external_typed_data.type = Dart_TypedData_kUint8;
1111 cbytes.value.as_external_typed_data.length = bytes_length;
1112 cbytes.value.as_external_typed_data.data = bytes;
1113 cbytes.value.as_external_typed_data.peer = bytes;
1114 cbytes.value.as_external_typed_data.callback = Finalizer;
1115
1116 Dart_CObject cstream_id;
1117 cstream_id.type = Dart_CObject_kString;
1118 cstream_id.value.as_string = const_cast<char*>(stream_id);
1119
1120 Dart_CObject* elements[2];
1121 elements[0] = &cstream_id;
1122 elements[1] = &cbytes;
1123 Dart_CObject message;
1124 message.type = Dart_CObject_kArray;
1125 message.value.as_array.length = 2;
1126 message.value.as_array.values = elements;
1127
1128 std::unique_ptr<Message> msg =
1129 WriteApiMessage(thread->zone(), &message, ServiceIsolate::Port(),
1130 Message::kNormalPriority);
1131 if (msg == nullptr) {
1132 result = false;
1133 } else {
1134 result = PortMap::PostMessage(std::move(msg));
1135 }
1136 }
1137
1138 if (!result) {
1139 free(bytes);
1140 }
1141}
1142
1143void Service::SendEventWithData(const char* stream_id,
1144 const char* event_type,
1145 intptr_t reservation,
1146 const char* metadata,
1147 intptr_t metadata_size,
1148 uint8_t* data,
1149 intptr_t data_size) {
1150 ASSERT(kInt32Size + metadata_size <= reservation);
1151 // Using a SPACE creates valid JSON. Our goal here is to prevent the memory
1152 // overhead of copying to concatenate metadata and payload together by
1153 // over-allocating to underlying buffer before we know how long the metadata
1154 // will be.
1155 memset(data, ' ', reservation);
1156 reinterpret_cast<uint32_t*>(data)[0] = reservation;
1157 memmove(&(reinterpret_cast<uint32_t*>(data)[1]), metadata, metadata_size);
1158 Service::SendEvent(stream_id, event_type, bytes: data, bytes_length: data_size);
1159}
1160
1161static void ReportPauseOnConsole(ServiceEvent* event) {
1162 const char* name = event->isolate()->name();
1163 const int64_t main_port = static_cast<int64_t>(event->isolate()->main_port());
1164 switch (event->kind()) {
1165 case ServiceEvent::kPauseStart:
1166 OS::PrintErr("vm-service: isolate(%" Pd64
1167 ") '%s' has no debugger attached and is paused at start.",
1168 main_port, name);
1169 break;
1170 case ServiceEvent::kPauseExit:
1171 OS::PrintErr("vm-service: isolate(%" Pd64
1172 ") '%s' has no debugger attached and is paused at exit.",
1173 main_port, name);
1174 break;
1175 case ServiceEvent::kPauseException:
1176 OS::PrintErr(
1177 "vm-service: isolate (%" Pd64
1178 ") '%s' has no debugger attached and is paused due to exception.",
1179 main_port, name);
1180 break;
1181 case ServiceEvent::kPauseInterrupted:
1182 OS::PrintErr(
1183 "vm-service: isolate (%" Pd64
1184 ") '%s' has no debugger attached and is paused due to interrupt.",
1185 main_port, name);
1186 break;
1187 case ServiceEvent::kPauseBreakpoint:
1188 OS::PrintErr("vm-service: isolate (%" Pd64
1189 ") '%s' has no debugger attached and is paused.",
1190 main_port, name);
1191 break;
1192 case ServiceEvent::kPausePostRequest:
1193 OS::PrintErr("vm-service: isolate (%" Pd64
1194 ") '%s' has no debugger attached and is paused post reload.",
1195 main_port, name);
1196 break;
1197 default:
1198 UNREACHABLE();
1199 break;
1200 }
1201 if (!ServiceIsolate::IsRunning()) {
1202 OS::PrintErr(format: " Start the vm-service to debug.\n");
1203 } else if (ServiceIsolate::server_address() == nullptr) {
1204 OS::PrintErr(format: " Connect to the Dart VM service to debug.\n");
1205 } else {
1206 OS::PrintErr(format: " Connect to the Dart VM service at %s to debug.\n",
1207 ServiceIsolate::server_address());
1208 }
1209 const Error& err = Error::Handle(ptr: Thread::Current()->sticky_error());
1210 if (!err.IsNull()) {
1211 OS::PrintErr(format: "%s\n", err.ToErrorCString());
1212 }
1213}
1214
1215void Service::HandleEvent(ServiceEvent* event, bool enter_safepoint) {
1216 if (event->stream_info() != nullptr && !event->stream_info()->enabled()) {
1217 if (FLAG_warn_on_pause_with_no_debugger && event->IsPause()) {
1218 // If we are about to pause a running program which has no
1219 // debugger connected, tell the user about it.
1220 ReportPauseOnConsole(event);
1221 }
1222 // Ignore events when no one is listening to the event stream.
1223 return;
1224 } else if (event->stream_info() != nullptr &&
1225 FLAG_warn_on_pause_with_no_debugger && event->IsPause()) {
1226 ReportPauseOnConsole(event);
1227 }
1228 if (!ServiceIsolate::IsRunning()) {
1229 return;
1230 }
1231 JSONStream js;
1232 if (event->stream_info() != nullptr) {
1233 js.set_include_private_members(
1234 event->stream_info()->include_private_members());
1235 }
1236 const char* stream_id = event->stream_id();
1237 ASSERT(stream_id != nullptr);
1238 {
1239 JSONObject jsobj(&js);
1240 jsobj.AddProperty(name: "jsonrpc", s: "2.0");
1241 jsobj.AddProperty(name: "method", s: "streamNotify");
1242 JSONObject params(&jsobj, "params");
1243 params.AddProperty(name: "streamId", s: stream_id);
1244 params.AddProperty(name: "event", event);
1245 }
1246 PostEvent(isolate_group: event->isolate_group(), isolate: event->isolate(), stream_id,
1247 kind: event->KindAsCString(), event: &js, enter_safepoint);
1248}
1249
1250void Service::PostEvent(IsolateGroup* isolate_group,
1251 Isolate* isolate,
1252 const char* stream_id,
1253 const char* kind,
1254 JSONStream* event,
1255 bool enter_safepoint) {
1256 if (enter_safepoint) {
1257 // Enter a safepoint so we don't block the mutator while processing
1258 // large events.
1259 TransitionToNative transition(Thread::Current());
1260 PostEventImpl(isolate_group, isolate, stream_id, kind, event);
1261 return;
1262 }
1263 PostEventImpl(isolate_group, isolate, stream_id, kind, event);
1264}
1265
1266void Service::PostEventImpl(IsolateGroup* isolate_group,
1267 Isolate* isolate,
1268 const char* stream_id,
1269 const char* kind,
1270 JSONStream* event) {
1271 ASSERT(stream_id != nullptr);
1272 ASSERT(kind != nullptr);
1273 ASSERT(event != nullptr);
1274
1275 if (FLAG_trace_service) {
1276 if (isolate != nullptr) {
1277 ASSERT(isolate_group != nullptr);
1278 OS::PrintErr(
1279 "vm-service: Pushing "
1280 "ServiceEvent(isolateGroupId='" ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING
1281 "', isolate='%s', isolateId='" ISOLATE_SERVICE_ID_FORMAT_STRING
1282 "', kind='%s') to stream %s\n",
1283 isolate_group->id(), isolate->name(),
1284 static_cast<int64_t>(isolate->main_port()), kind, stream_id);
1285 } else if (isolate_group != nullptr) {
1286 OS::PrintErr(
1287 "vm-service: Pushing "
1288 "ServiceEvent(isolateGroupId='" ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING
1289 "', kind='%s') to stream %s\n",
1290 isolate_group->id(), kind, stream_id);
1291 } else {
1292 OS::PrintErr(
1293 format: "vm-service: Pushing ServiceEvent(isolate='<no current isolate>', "
1294 "kind='%s') to stream %s\n",
1295 kind, stream_id);
1296 }
1297 }
1298
1299 Service::LogResponseSize(method: kind, js: event);
1300
1301 // Message is of the format [<stream id>, <json string>].
1302 //
1303 // Build the event message in the C heap to avoid dart heap
1304 // allocation. This method can be called while we have acquired a
1305 // direct pointer to typed data, so we can't allocate here.
1306 Dart_CObject list_cobj;
1307 Dart_CObject* list_values[2];
1308 list_cobj.type = Dart_CObject_kArray;
1309 list_cobj.value.as_array.length = 2;
1310 list_cobj.value.as_array.values = list_values;
1311
1312 Dart_CObject stream_id_cobj;
1313 stream_id_cobj.type = Dart_CObject_kString;
1314 stream_id_cobj.value.as_string = const_cast<char*>(stream_id);
1315 list_values[0] = &stream_id_cobj;
1316
1317 Dart_CObject json_cobj;
1318 json_cobj.type = Dart_CObject_kString;
1319 json_cobj.value.as_string = const_cast<char*>(event->ToCString());
1320 list_values[1] = &json_cobj;
1321
1322 AllocOnlyStackZone zone;
1323 std::unique_ptr<Message> msg =
1324 WriteApiMessage(zone.GetZone(), &list_cobj, ServiceIsolate::Port(),
1325 Message::kNormalPriority);
1326 if (msg != nullptr) {
1327 PortMap::PostMessage(std::move(msg));
1328 }
1329}
1330
1331class EmbedderServiceHandler {
1332 public:
1333 explicit EmbedderServiceHandler(const char* name)
1334 : name_(nullptr),
1335 callback_(nullptr),
1336 user_data_(nullptr),
1337 next_(nullptr) {
1338 ASSERT(name != nullptr);
1339 name_ = Utils::StrDup(s: name);
1340 }
1341
1342 ~EmbedderServiceHandler() { free(name_); }
1343
1344 const char* name() const { return name_; }
1345
1346 Dart_ServiceRequestCallback callback() const { return callback_; }
1347 void set_callback(Dart_ServiceRequestCallback callback) {
1348 callback_ = callback;
1349 }
1350
1351 void* user_data() const { return user_data_; }
1352 void set_user_data(void* user_data) { user_data_ = user_data; }
1353
1354 EmbedderServiceHandler* next() const { return next_; }
1355 void set_next(EmbedderServiceHandler* next) { next_ = next; }
1356
1357 private:
1358 char* name_;
1359 Dart_ServiceRequestCallback callback_;
1360 void* user_data_;
1361 EmbedderServiceHandler* next_;
1362};
1363
1364void Service::EmbedderHandleMessage(EmbedderServiceHandler* handler,
1365 JSONStream* js) {
1366 ASSERT(handler != nullptr);
1367 Dart_ServiceRequestCallback callback = handler->callback();
1368 ASSERT(callback != nullptr);
1369 const char* response = nullptr;
1370 bool success;
1371 {
1372 TransitionVMToNative transition(Thread::Current());
1373 success = callback(js->method(), js->param_keys(), js->param_values(),
1374 js->num_params(), handler->user_data(), &response);
1375 }
1376 ASSERT(response != nullptr);
1377 if (!success) {
1378 js->SetupError();
1379 }
1380 js->buffer()->AddString(s: response);
1381 js->PostReply();
1382 free(const_cast<char*>(response));
1383}
1384
1385void Service::RegisterIsolateEmbedderCallback(
1386 const char* name,
1387 Dart_ServiceRequestCallback callback,
1388 void* user_data) {
1389 if (name == nullptr) {
1390 return;
1391 }
1392 EmbedderServiceHandler* handler = FindIsolateEmbedderHandler(name);
1393 if (handler != nullptr) {
1394 // Update existing handler entry.
1395 handler->set_callback(callback);
1396 handler->set_user_data(user_data);
1397 return;
1398 }
1399 // Create a new handler.
1400 handler = new EmbedderServiceHandler(name);
1401 handler->set_callback(callback);
1402 handler->set_user_data(user_data);
1403
1404 // Insert into isolate_service_handler_head_ list.
1405 handler->set_next(isolate_service_handler_head_);
1406 isolate_service_handler_head_ = handler;
1407}
1408
1409EmbedderServiceHandler* Service::FindIsolateEmbedderHandler(const char* name) {
1410 EmbedderServiceHandler* current = isolate_service_handler_head_;
1411 while (current != nullptr) {
1412 if (strcmp(name, current->name()) == 0) {
1413 return current;
1414 }
1415 current = current->next();
1416 }
1417 return nullptr;
1418}
1419
1420void Service::RegisterRootEmbedderCallback(const char* name,
1421 Dart_ServiceRequestCallback callback,
1422 void* user_data) {
1423 if (name == nullptr) {
1424 return;
1425 }
1426 EmbedderServiceHandler* handler = FindRootEmbedderHandler(name);
1427 if (handler != nullptr) {
1428 // Update existing handler entry.
1429 handler->set_callback(callback);
1430 handler->set_user_data(user_data);
1431 return;
1432 }
1433 // Create a new handler.
1434 handler = new EmbedderServiceHandler(name);
1435 handler->set_callback(callback);
1436 handler->set_user_data(user_data);
1437
1438 // Insert into root_service_handler_head_ list.
1439 handler->set_next(root_service_handler_head_);
1440 root_service_handler_head_ = handler;
1441}
1442
1443void Service::SetEmbedderStreamCallbacks(
1444 Dart_ServiceStreamListenCallback listen_callback,
1445 Dart_ServiceStreamCancelCallback cancel_callback) {
1446 stream_listen_callback_ = listen_callback;
1447 stream_cancel_callback_ = cancel_callback;
1448}
1449
1450void Service::SetGetServiceAssetsCallback(
1451 Dart_GetVMServiceAssetsArchive get_service_assets) {
1452 get_service_assets_callback_ = get_service_assets;
1453}
1454
1455void Service::SetEmbedderInformationCallback(
1456 Dart_EmbedderInformationCallback callback) {
1457 embedder_information_callback_ = callback;
1458}
1459
1460int64_t Service::CurrentRSS() {
1461 if (embedder_information_callback_ == nullptr) {
1462 return -1;
1463 }
1464 Dart_EmbedderInformation info = {
1465 0, // version
1466 nullptr, // name
1467 0, // max_rss
1468 0 // current_rss
1469 };
1470 embedder_information_callback_(&info);
1471 ASSERT(info.version == DART_EMBEDDER_INFORMATION_CURRENT_VERSION);
1472 return info.current_rss;
1473}
1474
1475int64_t Service::MaxRSS() {
1476 if (embedder_information_callback_ == nullptr) {
1477 return -1;
1478 }
1479 Dart_EmbedderInformation info = {
1480 0, // version
1481 nullptr, // name
1482 0, // max_rss
1483 0 // current_rss
1484 };
1485 embedder_information_callback_(&info);
1486 ASSERT(info.version == DART_EMBEDDER_INFORMATION_CURRENT_VERSION);
1487 return info.max_rss;
1488}
1489
1490void Service::SetDartLibraryKernelForSources(const uint8_t* kernel_bytes,
1491 intptr_t kernel_length) {
1492 dart_library_kernel_ = kernel_bytes;
1493 dart_library_kernel_len_ = kernel_length;
1494}
1495
1496EmbedderServiceHandler* Service::FindRootEmbedderHandler(const char* name) {
1497 EmbedderServiceHandler* current = root_service_handler_head_;
1498 while (current != nullptr) {
1499 if (strcmp(name, current->name()) == 0) {
1500 return current;
1501 }
1502 current = current->next();
1503 }
1504 return nullptr;
1505}
1506
1507void Service::ScheduleExtensionHandler(const Instance& handler,
1508 const String& method_name,
1509 const Array& parameter_keys,
1510 const Array& parameter_values,
1511 const Instance& reply_port,
1512 const Instance& id) {
1513 ASSERT(!handler.IsNull());
1514 ASSERT(!method_name.IsNull());
1515 ASSERT(!parameter_keys.IsNull());
1516 ASSERT(!parameter_values.IsNull());
1517 ASSERT(!reply_port.IsNull());
1518 Isolate* isolate = Isolate::Current();
1519 ASSERT(isolate != nullptr);
1520 isolate->AppendServiceExtensionCall(closure: handler, method_name, parameter_keys,
1521 parameter_values, reply_port, id);
1522}
1523
1524static const MethodParameter* const get_isolate_params[] = {
1525 ISOLATE_PARAMETER,
1526 nullptr,
1527};
1528
1529static void GetIsolate(Thread* thread, JSONStream* js) {
1530 thread->isolate()->PrintJSON(stream: js, ref: false);
1531}
1532
1533static const MethodParameter* const get_isolate_group_params[] = {
1534 ISOLATE_GROUP_PARAMETER,
1535 nullptr,
1536};
1537
1538enum SentinelType {
1539 kCollectedSentinel,
1540 kExpiredSentinel,
1541 kFreeSentinel,
1542};
1543
1544static void PrintSentinel(JSONStream* js, SentinelType sentinel_type) {
1545 JSONObject jsobj(js);
1546 jsobj.AddProperty(name: "type", s: "Sentinel");
1547 switch (sentinel_type) {
1548 case kCollectedSentinel:
1549 jsobj.AddProperty(name: "kind", s: "Collected");
1550 jsobj.AddProperty(name: "valueAsString", s: "<collected>");
1551 break;
1552 case kExpiredSentinel:
1553 jsobj.AddProperty(name: "kind", s: "Expired");
1554 jsobj.AddProperty(name: "valueAsString", s: "<expired>");
1555 break;
1556 case kFreeSentinel:
1557 jsobj.AddProperty(name: "kind", s: "Free");
1558 jsobj.AddProperty(name: "valueAsString", s: "<free>");
1559 break;
1560 default:
1561 UNIMPLEMENTED();
1562 break;
1563 }
1564}
1565
1566static const MethodParameter* const
1567 set_stream_include_private_members_params[] = {
1568 NO_ISOLATE_PARAMETER,
1569 new BoolParameter("includePrivateMembers", true),
1570 nullptr,
1571};
1572
1573static void SetStreamIncludePrivateMembers(Thread* thread, JSONStream* js) {
1574 const char* stream_id = js->LookupParam(key: "streamId");
1575 if (stream_id == nullptr) {
1576 PrintMissingParamError(js, param: "streamId");
1577 return;
1578 }
1579 bool include_private_members =
1580 BoolParameter::Parse(value: js->LookupParam(key: "includePrivateMembers"), default_value: false);
1581 intptr_t num_streams = sizeof(streams_) / sizeof(streams_[0]);
1582 for (intptr_t i = 0; i < num_streams; i++) {
1583 if (strcmp(stream_id, streams_[i]->id()) == 0) {
1584 streams_[i]->set_include_private_members(include_private_members);
1585 break;
1586 }
1587 }
1588 PrintSuccess(js);
1589}
1590
1591static void ActOnIsolateGroup(JSONStream* js,
1592 std::function<void(IsolateGroup*)> visitor) {
1593 const String& prefix =
1594 String::Handle(ptr: String::New(ISOLATE_GROUP_SERVICE_ID_PREFIX));
1595
1596 const String& s =
1597 String::Handle(ptr: String::New(cstr: js->LookupParam(key: "isolateGroupId")));
1598 if (!s.StartsWith(other: prefix)) {
1599 PrintInvalidParamError(js, param: "isolateGroupId");
1600 return;
1601 }
1602 uint64_t isolate_group_id = UInt64Parameter::Parse(
1603 value: String::Handle(ptr: String::SubString(str: s, begin_index: prefix.Length())).ToCString());
1604 IsolateGroup::RunWithIsolateGroup(
1605 isolate_group_id,
1606 [&visitor](IsolateGroup* isolate_group) { visitor(isolate_group); },
1607 /*if_not_found=*/[&js]() { PrintSentinel(js, sentinel_type: kExpiredSentinel); });
1608}
1609
1610static void GetIsolateGroup(Thread* thread, JSONStream* js) {
1611 ActOnIsolateGroup(js, [&](IsolateGroup* isolate_group) {
1612 isolate_group->PrintJSON(stream: js, ref: false);
1613 });
1614}
1615
1616static const MethodParameter* const get_memory_usage_params[] = {
1617 ISOLATE_PARAMETER,
1618 nullptr,
1619};
1620
1621static void GetMemoryUsage(Thread* thread, JSONStream* js) {
1622 thread->isolate()->PrintMemoryUsageJSON(stream: js);
1623}
1624
1625static const MethodParameter* const get_isolate_group_memory_usage_params[] = {
1626 ISOLATE_GROUP_PARAMETER,
1627 nullptr,
1628};
1629
1630static void GetIsolateGroupMemoryUsage(Thread* thread, JSONStream* js) {
1631 ActOnIsolateGroup(js, [&](IsolateGroup* isolate_group) {
1632 isolate_group->PrintMemoryUsageJSON(stream: js);
1633 });
1634}
1635
1636static const MethodParameter* const get_isolate_pause_event_params[] = {
1637 ISOLATE_PARAMETER,
1638 nullptr,
1639};
1640
1641static void GetIsolatePauseEvent(Thread* thread, JSONStream* js) {
1642 thread->isolate()->PrintPauseEventJSON(stream: js);
1643}
1644
1645static const MethodParameter* const get_scripts_params[] = {
1646 RUNNABLE_ISOLATE_PARAMETER,
1647 nullptr,
1648};
1649
1650static void GetScripts(Thread* thread, JSONStream* js) {
1651 auto object_store = thread->isolate_group()->object_store();
1652 Zone* zone = thread->zone();
1653
1654 const auto& libs =
1655 GrowableObjectArray::Handle(zone, ptr: object_store->libraries());
1656 intptr_t num_libs = libs.Length();
1657
1658 Library& lib = Library::Handle(zone);
1659 Array& scripts = Array::Handle(zone);
1660 Script& script = Script::Handle(zone);
1661
1662 JSONObject jsobj(js);
1663 {
1664 jsobj.AddProperty(name: "type", s: "ScriptList");
1665 JSONArray script_array(&jsobj, "scripts");
1666 for (intptr_t i = 0; i < num_libs; i++) {
1667 lib ^= libs.At(i);
1668 ASSERT(!lib.IsNull());
1669 scripts = lib.LoadedScripts();
1670 for (intptr_t j = 0; j < scripts.Length(); j++) {
1671 script ^= scripts.At(j);
1672 ASSERT(!script.IsNull());
1673 script_array.AddValue(obj: script);
1674 }
1675 }
1676 }
1677}
1678
1679static const MethodParameter* const get_stack_params[] = {
1680 RUNNABLE_ISOLATE_PARAMETER,
1681 new UIntParameter("limit", false),
1682 nullptr,
1683};
1684
1685static void GetStack(Thread* thread, JSONStream* js) {
1686 if (CheckDebuggerDisabled(thread, js)) {
1687 return;
1688 }
1689 intptr_t limit = 0;
1690 bool has_limit = js->HasParam(key: "limit");
1691 if (has_limit) {
1692 limit = UIntParameter::Parse(value: js->LookupParam(key: "limit"));
1693 if (limit < 0) {
1694 PrintInvalidParamError(js, param: "limit");
1695 return;
1696 }
1697 }
1698 Isolate* isolate = thread->isolate();
1699 DebuggerStackTrace* stack = isolate->debugger()->StackTrace();
1700 DebuggerStackTrace* async_awaiter_stack =
1701 isolate->debugger()->AsyncAwaiterStackTrace();
1702
1703 // Do we want the complete script object and complete local variable objects?
1704 // This is true for dump requests.
1705 JSONObject jsobj(js);
1706 jsobj.AddProperty(name: "type", s: "Stack");
1707 {
1708 JSONArray jsarr(&jsobj, "frames");
1709
1710 intptr_t num_frames =
1711 has_limit ? Utils::Minimum(x: stack->Length(), y: limit) : stack->Length();
1712
1713 for (intptr_t i = 0; i < num_frames; i++) {
1714 ActivationFrame* frame = stack->FrameAt(i);
1715 JSONObject jsobj(&jsarr);
1716 frame->PrintToJSONObject(jsobj: &jsobj);
1717 jsobj.AddProperty(name: "index", i);
1718 }
1719 }
1720
1721 if (async_awaiter_stack != nullptr) {
1722 JSONArray jsarr(&jsobj, "asyncCausalFrames");
1723 intptr_t num_frames =
1724 has_limit ? Utils::Minimum(x: async_awaiter_stack->Length(), y: limit)
1725 : async_awaiter_stack->Length();
1726 for (intptr_t i = 0; i < num_frames; i++) {
1727 ActivationFrame* frame = async_awaiter_stack->FrameAt(i);
1728 JSONObject jsobj(&jsarr);
1729 frame->PrintToJSONObject(jsobj: &jsobj);
1730 jsobj.AddProperty(name: "index", i);
1731 }
1732 }
1733
1734 const bool truncated =
1735 (has_limit &&
1736 (limit < stack->Length() || (async_awaiter_stack != nullptr &&
1737 limit < async_awaiter_stack->Length())));
1738 jsobj.AddProperty(name: "truncated", b: truncated);
1739
1740 {
1741 MessageHandler::AcquiredQueues aq(isolate->message_handler());
1742 jsobj.AddProperty(name: "messages", queue: aq.queue());
1743 }
1744}
1745
1746static void HandleCommonEcho(JSONObject* jsobj, JSONStream* js) {
1747 jsobj->AddProperty(name: "type", s: "_EchoResponse");
1748 if (js->HasParam(key: "text")) {
1749 jsobj->AddProperty(name: "text", s: js->LookupParam(key: "text"));
1750 }
1751}
1752
1753void Service::SendEchoEvent(Isolate* isolate, const char* text) {
1754 JSONStream js;
1755 {
1756 JSONObject jsobj(&js);
1757 jsobj.AddProperty(name: "jsonrpc", s: "2.0");
1758 jsobj.AddProperty(name: "method", s: "streamNotify");
1759 {
1760 JSONObject params(&jsobj, "params");
1761 params.AddProperty(name: "streamId", s: echo_stream.id());
1762 {
1763 JSONObject event(&params, "event");
1764 event.AddProperty(name: "type", s: "Event");
1765 event.AddProperty(name: "kind", s: "_Echo");
1766 event.AddProperty(name: "isolate", isolate);
1767 if (text != nullptr) {
1768 event.AddProperty(name: "text", s: text);
1769 }
1770 event.AddPropertyTimeMillis(name: "timestamp", millis: OS::GetCurrentTimeMillis());
1771 }
1772 }
1773 }
1774
1775 intptr_t reservation = js.buffer()->length() + sizeof(int32_t);
1776 intptr_t data_size = reservation + 3;
1777 uint8_t* data = reinterpret_cast<uint8_t*>(malloc(size: data_size));
1778 data[reservation + 0] = 0;
1779 data[reservation + 1] = 128;
1780 data[reservation + 2] = 255;
1781 SendEventWithData(stream_id: echo_stream.id(), event_type: "_Echo", reservation,
1782 metadata: js.buffer()->buffer(), metadata_size: js.buffer()->length(), data,
1783 data_size);
1784}
1785
1786static void TriggerEchoEvent(Thread* thread, JSONStream* js) {
1787 if (Service::echo_stream.enabled()) {
1788 Service::SendEchoEvent(isolate: thread->isolate(), text: js->LookupParam(key: "text"));
1789 }
1790 JSONObject jsobj(js);
1791 HandleCommonEcho(jsobj: &jsobj, js);
1792}
1793
1794static void Echo(Thread* thread, JSONStream* js) {
1795 JSONObject jsobj(js);
1796 HandleCommonEcho(jsobj: &jsobj, js);
1797}
1798
1799static bool ContainsNonInstance(const Object& obj) {
1800 if (obj.IsArray()) {
1801 const Array& array = Array::Cast(obj);
1802 Object& element = Object::Handle();
1803 for (intptr_t i = 0; i < array.Length(); ++i) {
1804 element = array.At(i);
1805 if (!(element.IsInstance() || element.IsNull())) {
1806 return true;
1807 }
1808 }
1809 return false;
1810 } else if (obj.IsGrowableObjectArray()) {
1811 const GrowableObjectArray& array = GrowableObjectArray::Cast(obj);
1812 Object& element = Object::Handle();
1813 for (intptr_t i = 0; i < array.Length(); ++i) {
1814 element = array.At(index: i);
1815 if (!(element.IsInstance() || element.IsNull())) {
1816 return true;
1817 }
1818 }
1819 return false;
1820 } else {
1821 return !(obj.IsInstance() || obj.IsNull());
1822 }
1823}
1824
1825static ObjectPtr LookupObjectId(Thread* thread,
1826 const char* arg,
1827 ObjectIdRing::LookupResult* kind) {
1828 *kind = ObjectIdRing::kValid;
1829 if (strncmp(arg, "int-", 4) == 0) {
1830 arg += 4;
1831 int64_t value = 0;
1832 if (!OS::StringToInt64(str: arg, value: &value) || !Smi::IsValid(value)) {
1833 *kind = ObjectIdRing::kInvalid;
1834 return Object::null();
1835 }
1836 const Integer& obj =
1837 Integer::Handle(thread->zone(), Smi::New(value: static_cast<intptr_t>(value)));
1838 return obj.ptr();
1839 } else if (strcmp(arg, "bool-true") == 0) {
1840 return Bool::True().ptr();
1841 } else if (strcmp(arg, "bool-false") == 0) {
1842 return Bool::False().ptr();
1843 } else if (strcmp(arg, "null") == 0) {
1844 return Object::null();
1845 }
1846
1847 ObjectIdRing* ring = thread->isolate()->EnsureObjectIdRing();
1848 intptr_t id = -1;
1849 if (!GetIntegerId(s: arg, id: &id)) {
1850 *kind = ObjectIdRing::kInvalid;
1851 return Object::null();
1852 }
1853 return ring->GetObjectForId(id, kind);
1854}
1855
1856static ObjectPtr LookupClassMembers(Thread* thread,
1857 const Class& klass,
1858 char** parts,
1859 int num_parts) {
1860 auto zone = thread->zone();
1861
1862 if (num_parts != 4) {
1863 return Object::sentinel().ptr();
1864 }
1865
1866 const char* encoded_id = parts[3];
1867 auto& id = String::Handle(ptr: String::New(cstr: encoded_id));
1868 id = String::DecodeIRI(str: id);
1869 if (id.IsNull()) {
1870 return Object::sentinel().ptr();
1871 }
1872
1873 if (strcmp(parts[2], "fields") == 0) {
1874 // Field ids look like: "classes/17/fields/name"
1875 const auto& field = Field::Handle(ptr: klass.LookupField(name: id));
1876 if (field.IsNull()) {
1877 return Object::sentinel().ptr();
1878 }
1879 return field.ptr();
1880 }
1881 if (strcmp(parts[2], "field_inits") == 0) {
1882 // Field initializer ids look like: "classes/17/field_inits/name"
1883 const auto& field = Field::Handle(ptr: klass.LookupField(name: id));
1884 if (field.IsNull() || (field.is_late() && !field.has_initializer())) {
1885 return Object::sentinel().ptr();
1886 }
1887 const auto& function = Function::Handle(ptr: field.EnsureInitializerFunction());
1888 if (function.IsNull()) {
1889 return Object::sentinel().ptr();
1890 }
1891 return function.ptr();
1892 }
1893 if (strcmp(parts[2], "functions") == 0) {
1894 // Function ids look like: "classes/17/functions/name"
1895
1896 const auto& function =
1897 Function::Handle(Resolver::ResolveFunction(zone: zone, receiver_class: klass, function_name: id));
1898 if (function.IsNull()) {
1899 return Object::sentinel().ptr();
1900 }
1901 return function.ptr();
1902 }
1903 if (strcmp(parts[2], "implicit_closures") == 0) {
1904 // Function ids look like: "classes/17/implicit_closures/11"
1905 intptr_t id;
1906 if (!GetIntegerId(s: parts[3], id: &id)) {
1907 return Object::sentinel().ptr();
1908 }
1909 const auto& func =
1910 Function::Handle(zone, klass.ImplicitClosureFunctionFromIndex(idx: id));
1911 if (func.IsNull()) {
1912 return Object::sentinel().ptr();
1913 }
1914 return func.ptr();
1915 }
1916 if (strcmp(parts[2], "dispatchers") == 0) {
1917 // Dispatcher Function ids look like: "classes/17/dispatchers/11"
1918 intptr_t id;
1919 if (!GetIntegerId(s: parts[3], id: &id)) {
1920 return Object::sentinel().ptr();
1921 }
1922 const auto& func =
1923 Function::Handle(zone, klass.InvocationDispatcherFunctionFromIndex(idx: id));
1924 if (func.IsNull()) {
1925 return Object::sentinel().ptr();
1926 }
1927 return func.ptr();
1928 }
1929 if (strcmp(parts[2], "closures") == 0) {
1930 // Closure ids look like: "classes/17/closures/11"
1931 intptr_t id;
1932 if (!GetIntegerId(s: parts[3], id: &id)) {
1933 return Object::sentinel().ptr();
1934 }
1935 Function& func = Function::Handle(zone);
1936 func = ClosureFunctionsCache::ClosureFunctionFromIndex(idx: id);
1937 if (func.IsNull()) {
1938 return Object::sentinel().ptr();
1939 }
1940 return func.ptr();
1941 }
1942
1943 UNREACHABLE();
1944 return Object::sentinel().ptr();
1945}
1946
1947static ObjectPtr LookupHeapObjectLibraries(IsolateGroup* isolate_group,
1948 char** parts,
1949 int num_parts) {
1950 // Library ids look like "libraries/35"
1951 if (num_parts < 2) {
1952 return Object::sentinel().ptr();
1953 }
1954 const auto& libs =
1955 GrowableObjectArray::Handle(ptr: isolate_group->object_store()->libraries());
1956 ASSERT(!libs.IsNull());
1957 const String& id = String::Handle(ptr: String::New(cstr: parts[1]));
1958 // Scan for private key.
1959 String& private_key = String::Handle();
1960 Library& lib = Library::Handle();
1961 bool lib_found = false;
1962 for (intptr_t i = 0; i < libs.Length(); i++) {
1963 lib ^= libs.At(index: i);
1964 ASSERT(!lib.IsNull());
1965 private_key = lib.private_key();
1966 if (private_key.Equals(str: id)) {
1967 lib_found = true;
1968 break;
1969 }
1970 }
1971 if (!lib_found) {
1972 return Object::sentinel().ptr();
1973 }
1974
1975 const auto& klass = Class::Handle(ptr: lib.toplevel_class());
1976 ASSERT(!klass.IsNull());
1977
1978 if (num_parts == 2) {
1979 return lib.ptr();
1980 }
1981 if (strcmp(parts[2], "fields") == 0) {
1982 // Library field ids look like: "libraries/17/fields/name"
1983 return LookupClassMembers(thread: Thread::Current(), klass, parts, num_parts);
1984 }
1985 if (strcmp(parts[2], "field_inits") == 0) {
1986 // Library field ids look like: "libraries/17/field_inits/name"
1987 return LookupClassMembers(thread: Thread::Current(), klass, parts, num_parts);
1988 }
1989 if (strcmp(parts[2], "functions") == 0) {
1990 // Library function ids look like: "libraries/17/functions/name"
1991 return LookupClassMembers(thread: Thread::Current(), klass, parts, num_parts);
1992 }
1993 if (strcmp(parts[2], "closures") == 0) {
1994 // Library function ids look like: "libraries/17/closures/name"
1995 return LookupClassMembers(thread: Thread::Current(), klass, parts, num_parts);
1996 }
1997 if (strcmp(parts[2], "implicit_closures") == 0) {
1998 // Library function ids look like: "libraries/17/implicit_closures/name"
1999 return LookupClassMembers(thread: Thread::Current(), klass, parts, num_parts);
2000 }
2001
2002 if (strcmp(parts[2], "scripts") == 0) {
2003 // Script ids look like "libraries/35/scripts/library%2Furl.dart/12345"
2004 if (num_parts != 5) {
2005 return Object::sentinel().ptr();
2006 }
2007 const String& id = String::Handle(ptr: String::New(cstr: parts[3]));
2008 ASSERT(!id.IsNull());
2009 // The id is the url of the script % encoded, decode it.
2010 const String& requested_url = String::Handle(ptr: String::DecodeIRI(str: id));
2011
2012 // Each script id is tagged with a load time.
2013 int64_t timestamp;
2014 if (!GetInteger64Id(s: parts[4], id: &timestamp, base: 16) || (timestamp < 0)) {
2015 return Object::sentinel().ptr();
2016 }
2017
2018 Script& script = Script::Handle();
2019 String& script_url = String::Handle();
2020 const Array& loaded_scripts = Array::Handle(ptr: lib.LoadedScripts());
2021 ASSERT(!loaded_scripts.IsNull());
2022 intptr_t i;
2023 for (i = 0; i < loaded_scripts.Length(); i++) {
2024 script ^= loaded_scripts.At(i);
2025 ASSERT(!script.IsNull());
2026 script_url = script.url();
2027 if (script_url.Equals(str: requested_url) &&
2028 (timestamp == script.load_timestamp())) {
2029 return script.ptr();
2030 }
2031 }
2032 }
2033
2034 // Not found.
2035 return Object::sentinel().ptr();
2036}
2037
2038static ObjectPtr LookupHeapObjectClasses(Thread* thread,
2039 char** parts,
2040 int num_parts) {
2041 // Class ids look like: "classes/17"
2042 if (num_parts < 2) {
2043 return Object::sentinel().ptr();
2044 }
2045 Zone* zone = thread->zone();
2046 auto table = thread->isolate_group()->class_table();
2047 intptr_t id;
2048 if (!GetIntegerId(s: parts[1], id: &id) || !table->IsValidIndex(cid: id)) {
2049 return Object::sentinel().ptr();
2050 }
2051 Class& cls = Class::Handle(zone, ptr: table->At(cid: id));
2052 if (num_parts == 2) {
2053 return cls.ptr();
2054 }
2055 if (strcmp(parts[2], "closures") == 0) {
2056 // Closure ids look like: "classes/17/closures/11"
2057 return LookupClassMembers(thread, klass: cls, parts, num_parts);
2058 } else if (strcmp(parts[2], "field_inits") == 0) {
2059 // Field initializer ids look like: "classes/17/field_inits/name"
2060 return LookupClassMembers(thread, klass: cls, parts, num_parts);
2061 } else if (strcmp(parts[2], "fields") == 0) {
2062 // Field ids look like: "classes/17/fields/name"
2063 return LookupClassMembers(thread, klass: cls, parts, num_parts);
2064 } else if (strcmp(parts[2], "functions") == 0) {
2065 // Function ids look like: "classes/17/functions/name"
2066 return LookupClassMembers(thread, klass: cls, parts, num_parts);
2067 } else if (strcmp(parts[2], "implicit_closures") == 0) {
2068 // Function ids look like: "classes/17/implicit_closures/11"
2069 return LookupClassMembers(thread, klass: cls, parts, num_parts);
2070 } else if (strcmp(parts[2], "dispatchers") == 0) {
2071 // Dispatcher Function ids look like: "classes/17/dispatchers/11"
2072 return LookupClassMembers(thread, klass: cls, parts, num_parts);
2073 } else if (strcmp(parts[2], "types") == 0) {
2074 // Type ids look like: "classes/17/types/11"
2075 if (num_parts != 4) {
2076 return Object::sentinel().ptr();
2077 }
2078 intptr_t id;
2079 if (!GetIntegerId(s: parts[3], id: &id)) {
2080 return Object::sentinel().ptr();
2081 }
2082 if (id != 0) {
2083 return Object::sentinel().ptr();
2084 }
2085 const Type& type = Type::Handle(zone, ptr: cls.DeclarationType());
2086 if (!type.IsNull()) {
2087 return type.ptr();
2088 }
2089 }
2090
2091 // Not found.
2092 return Object::sentinel().ptr();
2093}
2094
2095static ObjectPtr LookupHeapObjectTypeArguments(Thread* thread,
2096 char** parts,
2097 int num_parts) {
2098 // TypeArguments ids look like: "typearguments/17"
2099 if (num_parts < 2) {
2100 return Object::sentinel().ptr();
2101 }
2102 intptr_t id;
2103 if (!GetIntegerId(s: parts[1], id: &id)) {
2104 return Object::sentinel().ptr();
2105 }
2106 ObjectStore* object_store = thread->isolate_group()->object_store();
2107 const Array& table =
2108 Array::Handle(thread->zone(), object_store->canonical_type_arguments());
2109 ASSERT(table.Length() > 0);
2110 const intptr_t table_size = table.Length() - 1;
2111 if ((id < 0) || (id >= table_size) || (table.At(id) == Object::null())) {
2112 return Object::sentinel().ptr();
2113 }
2114 return table.At(id);
2115}
2116
2117static ObjectPtr LookupHeapObjectCode(char** parts, int num_parts) {
2118 if (num_parts != 2) {
2119 return Object::sentinel().ptr();
2120 }
2121 uword pc;
2122 const char* const kCollectedPrefix = "collected-";
2123 const intptr_t kCollectedPrefixLen = strlen(kCollectedPrefix);
2124 const char* const kNativePrefix = "native-";
2125 const intptr_t kNativePrefixLen = strlen(kNativePrefix);
2126 const char* const kReusedPrefix = "reused-";
2127 const intptr_t kReusedPrefixLen = strlen(kReusedPrefix);
2128 const char* id = parts[1];
2129 if (strncmp(kCollectedPrefix, id, kCollectedPrefixLen) == 0) {
2130 if (!GetUnsignedIntegerId(s: &id[kCollectedPrefixLen], id: &pc, base: 16)) {
2131 return Object::sentinel().ptr();
2132 }
2133 // TODO(turnidge): Return "collected" instead.
2134 return Object::null();
2135 }
2136 if (strncmp(kNativePrefix, id, kNativePrefixLen) == 0) {
2137 if (!GetUnsignedIntegerId(s: &id[kNativePrefixLen], id: &pc, base: 16)) {
2138 return Object::sentinel().ptr();
2139 }
2140 // TODO(johnmccutchan): Support native Code.
2141 return Object::null();
2142 }
2143 if (strncmp(kReusedPrefix, id, kReusedPrefixLen) == 0) {
2144 if (!GetUnsignedIntegerId(s: &id[kReusedPrefixLen], id: &pc, base: 16)) {
2145 return Object::sentinel().ptr();
2146 }
2147 // TODO(turnidge): Return "expired" instead.
2148 return Object::null();
2149 }
2150 int64_t timestamp = 0;
2151 if (!GetCodeId(s: id, timestamp: &timestamp, address: &pc) || (timestamp < 0)) {
2152 return Object::sentinel().ptr();
2153 }
2154 Code& code = Code::Handle(ptr: Code::FindCode(pc, timestamp));
2155 if (!code.IsNull()) {
2156 return code.ptr();
2157 }
2158
2159 // Not found.
2160 return Object::sentinel().ptr();
2161}
2162
2163static ObjectPtr LookupHeapObjectMessage(Thread* thread,
2164 char** parts,
2165 int num_parts) {
2166 if (num_parts != 2) {
2167 return Object::sentinel().ptr();
2168 }
2169 uword message_id = 0;
2170 if (!GetUnsignedIntegerId(s: parts[1], id: &message_id, base: 16)) {
2171 return Object::sentinel().ptr();
2172 }
2173 MessageHandler::AcquiredQueues aq(thread->isolate()->message_handler());
2174 Message* message = aq.queue()->FindMessageById(id: message_id);
2175 if (message == nullptr) {
2176 // The user may try to load an expired message.
2177 return Object::sentinel().ptr();
2178 }
2179 if (message->IsRaw()) {
2180 return message->raw_obj();
2181 } else {
2182 return ReadMessage(thread, message);
2183 }
2184}
2185
2186static ObjectPtr LookupHeapObject(Thread* thread,
2187 const char* id_original,
2188 ObjectIdRing::LookupResult* result) {
2189 char* id = thread->zone()->MakeCopyOfString(id_original);
2190
2191 // Parse the id by splitting at each '/'.
2192 const int MAX_PARTS = 8;
2193 char* parts[MAX_PARTS];
2194 int num_parts = 0;
2195 int i = 0;
2196 int start_pos = 0;
2197 while (id[i] != '\0') {
2198 if (id[i] == '/') {
2199 id[i++] = '\0';
2200 parts[num_parts++] = &id[start_pos];
2201 if (num_parts == MAX_PARTS) {
2202 break;
2203 }
2204 start_pos = i;
2205 } else {
2206 i++;
2207 }
2208 }
2209 if (num_parts < MAX_PARTS) {
2210 parts[num_parts++] = &id[start_pos];
2211 }
2212
2213 if (result != nullptr) {
2214 *result = ObjectIdRing::kValid;
2215 }
2216
2217 Isolate* isolate = thread->isolate();
2218 if (strcmp(parts[0], "objects") == 0) {
2219 // Object ids look like "objects/1123"
2220 Object& obj = Object::Handle(thread->zone());
2221 ObjectIdRing::LookupResult lookup_result;
2222 obj = LookupObjectId(thread, arg: parts[1], kind: &lookup_result);
2223 if (lookup_result != ObjectIdRing::kValid) {
2224 if (result != nullptr) {
2225 *result = lookup_result;
2226 }
2227 return Object::sentinel().ptr();
2228 }
2229 return obj.ptr();
2230
2231 } else if (strcmp(parts[0], "libraries") == 0) {
2232 return LookupHeapObjectLibraries(isolate_group: isolate->group(), parts, num_parts);
2233 } else if (strcmp(parts[0], "classes") == 0) {
2234 return LookupHeapObjectClasses(thread, parts, num_parts);
2235 } else if (strcmp(parts[0], "typearguments") == 0) {
2236 return LookupHeapObjectTypeArguments(thread, parts, num_parts);
2237 } else if (strcmp(parts[0], "code") == 0) {
2238 return LookupHeapObjectCode(parts, num_parts);
2239 } else if (strcmp(parts[0], "messages") == 0) {
2240 return LookupHeapObjectMessage(thread, parts, num_parts);
2241 }
2242
2243 // Not found.
2244 return Object::sentinel().ptr();
2245}
2246
2247static Breakpoint* LookupBreakpoint(Isolate* isolate,
2248 const char* id,
2249 ObjectIdRing::LookupResult* result) {
2250 *result = ObjectIdRing::kInvalid;
2251 size_t end_pos = strcspn(id, "/");
2252 if (end_pos == strlen(id)) {
2253 return nullptr;
2254 }
2255 const char* rest = id + end_pos + 1; // +1 for '/'.
2256 if (strncmp("breakpoints", id, end_pos) == 0) {
2257 intptr_t bpt_id = 0;
2258 Breakpoint* bpt = nullptr;
2259 if (GetIntegerId(s: rest, id: &bpt_id)) {
2260 bpt = isolate->debugger()->GetBreakpointById(id: bpt_id);
2261 if (bpt != nullptr) {
2262 *result = ObjectIdRing::kValid;
2263 return bpt;
2264 }
2265 if (bpt_id < isolate->debugger()->limitBreakpointId()) {
2266 *result = ObjectIdRing::kCollected;
2267 return nullptr;
2268 }
2269 }
2270 }
2271 return nullptr;
2272}
2273
2274static inline void AddParentFieldToResponseBasedOnRecord(
2275 Thread* thread,
2276 Array* field_names_handle,
2277 String* name_handle,
2278 const JSONObject& jsresponse,
2279 const Record& record,
2280 const intptr_t field_slot_offset) {
2281 *field_names_handle = record.GetFieldNames(thread);
2282 const intptr_t num_positional_fields =
2283 record.num_fields() - field_names_handle->Length();
2284 const intptr_t field_index =
2285 (field_slot_offset - Record::field_offset(index: 0)) / Record::kBytesPerElement;
2286 if (field_index < num_positional_fields) {
2287 jsresponse.AddProperty(name: "parentField", i: field_index);
2288 } else {
2289 *name_handle ^= field_names_handle->At(field_index - num_positional_fields);
2290 jsresponse.AddProperty(name: "parentField", s: name_handle->ToCString());
2291 }
2292}
2293
2294static void PrintInboundReferences(Thread* thread,
2295 Object* target,
2296 intptr_t limit,
2297 JSONStream* js) {
2298 ObjectGraph graph(thread);
2299 Array& path = Array::Handle(ptr: Array::New(len: limit * 2));
2300 intptr_t length = graph.InboundReferences(obj: target, references: path);
2301 OffsetsTable offsets_table(thread->zone());
2302 JSONObject jsobj(js);
2303 jsobj.AddProperty(name: "type", s: "InboundReferences");
2304 {
2305 JSONArray elements(&jsobj, "references");
2306 Object& source = Object::Handle();
2307 Smi& slot_offset = Smi::Handle();
2308 Array& field_names = Array::Handle();
2309 String& name = String::Handle();
2310 Class& source_class = Class::Handle();
2311 Field& field = Field::Handle();
2312 Array& parent_field_map = Array::Handle();
2313 limit = Utils::Minimum(x: limit, y: length);
2314 for (intptr_t i = 0; i < limit; ++i) {
2315 JSONObject jselement(&elements);
2316 source = path.At(i * 2);
2317 slot_offset ^= path.At((i * 2) + 1);
2318
2319 jselement.AddProperty(name: "source", obj: source);
2320 if (source.IsArray()) {
2321 intptr_t element_index =
2322 (slot_offset.Value() - Array::element_offset(index: 0)) /
2323 Array::kBytesPerElement;
2324 jselement.AddProperty(name: "parentListIndex", i: element_index);
2325 jselement.AddProperty(name: "parentField", i: element_index);
2326 } else if (source.IsRecord()) {
2327 AddParentFieldToResponseBasedOnRecord(thread, field_names_handle: &field_names, name_handle: &name,
2328 jsresponse: jselement, record: Record::Cast(obj: source),
2329 field_slot_offset: slot_offset.Value());
2330 } else {
2331 if (source.IsInstance()) {
2332 source_class = source.clazz();
2333 parent_field_map = source_class.OffsetToFieldMap();
2334 intptr_t index = slot_offset.Value() >> kCompressedWordSizeLog2;
2335 if (index > 0 && index < parent_field_map.Length()) {
2336 field ^= parent_field_map.At(index);
2337 if (!field.IsNull()) {
2338 jselement.AddProperty(name: "parentField", obj: field);
2339 continue;
2340 }
2341 }
2342 }
2343 const char* field_name = offsets_table.FieldNameForOffset(
2344 cid: source.GetClassId(), offset: slot_offset.Value());
2345 if (field_name != nullptr) {
2346 jselement.AddProperty(name: "_parentWordOffset", i: slot_offset.Value());
2347 // TODO(vm-service): Adjust RPC type to allow returning a field name
2348 // without a field object, or reify the fields described by
2349 // raw_object_fields.cc
2350 // jselement.AddProperty("_parentFieldName", field_name);
2351 } else if (source.IsContext()) {
2352 intptr_t element_index =
2353 (slot_offset.Value() - Context::variable_offset(context_index: 0)) /
2354 Context::kBytesPerElement;
2355 jselement.AddProperty(name: "parentListIndex", i: element_index);
2356 jselement.AddProperty(name: "parentField", i: element_index);
2357 } else {
2358 jselement.AddProperty(name: "_parentWordOffset", i: slot_offset.Value());
2359 }
2360 }
2361 }
2362 }
2363
2364 // We nil out the array after generating the response to prevent
2365 // reporting spurious references when repeatedly looking for the
2366 // references to an object.
2367 for (intptr_t i = 0; i < path.Length(); i++) {
2368 path.SetAt(i, Object::null_object());
2369 }
2370}
2371
2372static const MethodParameter* const get_inbound_references_params[] = {
2373 RUNNABLE_ISOLATE_PARAMETER,
2374 nullptr,
2375};
2376
2377static void GetInboundReferences(Thread* thread, JSONStream* js) {
2378 const char* target_id = js->LookupParam(key: "targetId");
2379 if (target_id == nullptr) {
2380 PrintMissingParamError(js, param: "targetId");
2381 return;
2382 }
2383 const char* limit_cstr = js->LookupParam(key: "limit");
2384 if (limit_cstr == nullptr) {
2385 PrintMissingParamError(js, param: "limit");
2386 return;
2387 }
2388 intptr_t limit;
2389 if (!GetIntegerId(s: limit_cstr, id: &limit)) {
2390 PrintInvalidParamError(js, param: "limit");
2391 return;
2392 }
2393
2394 Object& obj = Object::Handle(thread->zone());
2395 ObjectIdRing::LookupResult lookup_result;
2396 {
2397 HANDLESCOPE(thread);
2398 obj = LookupHeapObject(thread, id_original: target_id, result: &lookup_result);
2399 }
2400 if (obj.ptr() == Object::sentinel().ptr()) {
2401 if (lookup_result == ObjectIdRing::kCollected) {
2402 PrintSentinel(js, sentinel_type: kCollectedSentinel);
2403 } else if (lookup_result == ObjectIdRing::kExpired) {
2404 PrintSentinel(js, sentinel_type: kExpiredSentinel);
2405 } else {
2406 PrintInvalidParamError(js, param: "targetId");
2407 }
2408 return;
2409 }
2410 PrintInboundReferences(thread, target: &obj, limit, js);
2411}
2412
2413static void PrintRetainingPath(Thread* thread,
2414 Object* obj,
2415 intptr_t limit,
2416 JSONStream* js) {
2417 ObjectGraph graph(thread);
2418 Array& path = Array::Handle(ptr: Array::New(len: limit * 2));
2419 auto result = graph.RetainingPath(obj, path);
2420 intptr_t length = result.length;
2421 JSONObject jsobj(js);
2422 jsobj.AddProperty(name: "type", s: "RetainingPath");
2423 jsobj.AddProperty(name: "length", i: length);
2424 jsobj.AddProperty(name: "gcRootType", s: result.gc_root_type);
2425 JSONArray elements(&jsobj, "elements");
2426 Object& element = Object::Handle();
2427 Smi& slot_offset = Smi::Handle();
2428 Array& field_names = Array::Handle();
2429 String& name = String::Handle();
2430 Class& element_class = Class::Handle();
2431 Array& element_field_map = Array::Handle();
2432 Map& map = Map::Handle();
2433 Array& map_data = Array::Handle();
2434 Field& field = Field::Handle();
2435 WeakProperty& wp = WeakProperty::Handle();
2436 limit = Utils::Minimum(x: limit, y: length);
2437 OffsetsTable offsets_table(thread->zone());
2438 for (intptr_t i = 0; i < limit; ++i) {
2439 JSONObject jselement(&elements);
2440 element = path.At(i * 2);
2441 jselement.AddProperty(name: "value", obj: element);
2442 // Interpret the word offset from parent as list index, map key,
2443 // weak property, or instance field.
2444 if (i > 0) {
2445 slot_offset ^= path.At((i * 2) - 1);
2446 if (element.IsArray() || element.IsGrowableObjectArray()) {
2447 intptr_t element_index =
2448 (slot_offset.Value() - Array::element_offset(index: 0)) /
2449 Array::kBytesPerElement;
2450 jselement.AddProperty(name: "parentListIndex", i: element_index);
2451 jselement.AddProperty(name: "parentField", i: element_index);
2452 } else if (element.IsRecord()) {
2453 AddParentFieldToResponseBasedOnRecord(thread, field_names_handle: &field_names, name_handle: &name,
2454 jsresponse: jselement, record: Record::Cast(obj: element),
2455 field_slot_offset: slot_offset.Value());
2456 } else if (element.IsMap()) {
2457 map = static_cast<MapPtr>(path.At(i * 2));
2458 map_data = map.data();
2459 intptr_t element_index =
2460 (slot_offset.Value() - Array::element_offset(index: 0)) /
2461 Array::kBytesPerElement;
2462 Map::Iterator iterator(map);
2463 while (iterator.MoveNext()) {
2464 if (iterator.CurrentKey() == map_data.At(element_index) ||
2465 iterator.CurrentValue() == map_data.At(element_index)) {
2466 element = iterator.CurrentKey();
2467 jselement.AddProperty(name: "parentMapKey", obj: element);
2468 break;
2469 }
2470 }
2471 } else if (element.IsWeakProperty()) {
2472 wp ^= static_cast<WeakPropertyPtr>(element.ptr());
2473 element = wp.key();
2474 jselement.AddProperty(name: "parentMapKey", obj: element);
2475 } else {
2476 if (element.IsInstance()) {
2477 element_class = element.clazz();
2478 element_field_map = element_class.OffsetToFieldMap();
2479 intptr_t index = slot_offset.Value() >> kCompressedWordSizeLog2;
2480 if ((index > 0) && (index < element_field_map.Length())) {
2481 field ^= element_field_map.At(index);
2482 if (!field.IsNull()) {
2483 name ^= field.name();
2484 jselement.AddProperty(name: "parentField", s: name.ToCString());
2485 continue;
2486 }
2487 }
2488 }
2489 const char* field_name = offsets_table.FieldNameForOffset(
2490 cid: element.GetClassId(), offset: slot_offset.Value());
2491 if (field_name != nullptr) {
2492 jselement.AddProperty(name: "parentField", s: field_name);
2493 } else if (element.IsContext()) {
2494 intptr_t element_index =
2495 (slot_offset.Value() - Context::variable_offset(context_index: 0)) /
2496 Context::kBytesPerElement;
2497 jselement.AddProperty(name: "parentListIndex", i: element_index);
2498 jselement.AddProperty(name: "parentField", i: element_index);
2499 } else {
2500 jselement.AddProperty(name: "_parentWordOffset", i: slot_offset.Value());
2501 }
2502 }
2503 }
2504 }
2505
2506 // We nil out the array after generating the response to prevent
2507 // reporting spurious references when looking for inbound references
2508 // after looking for a retaining path.
2509 for (intptr_t i = 0; i < path.Length(); i++) {
2510 path.SetAt(i, Object::null_object());
2511 }
2512}
2513
2514static const MethodParameter* const get_retaining_path_params[] = {
2515 RUNNABLE_ISOLATE_PARAMETER,
2516 nullptr,
2517};
2518
2519static void GetRetainingPath(Thread* thread, JSONStream* js) {
2520 const char* target_id = js->LookupParam(key: "targetId");
2521 if (target_id == nullptr) {
2522 PrintMissingParamError(js, param: "targetId");
2523 return;
2524 }
2525 const char* limit_cstr = js->LookupParam(key: "limit");
2526 if (limit_cstr == nullptr) {
2527 PrintMissingParamError(js, param: "limit");
2528 return;
2529 }
2530 intptr_t limit;
2531 if (!GetIntegerId(s: limit_cstr, id: &limit)) {
2532 PrintInvalidParamError(js, param: "limit");
2533 return;
2534 }
2535
2536 Object& obj = Object::Handle(thread->zone());
2537 ObjectIdRing::LookupResult lookup_result;
2538 {
2539 HANDLESCOPE(thread);
2540 obj = LookupHeapObject(thread, id_original: target_id, result: &lookup_result);
2541 }
2542 if (obj.ptr() == Object::sentinel().ptr()) {
2543 if (lookup_result == ObjectIdRing::kCollected) {
2544 PrintSentinel(js, sentinel_type: kCollectedSentinel);
2545 } else if (lookup_result == ObjectIdRing::kExpired) {
2546 PrintSentinel(js, sentinel_type: kExpiredSentinel);
2547 } else {
2548 PrintInvalidParamError(js, param: "targetId");
2549 }
2550 return;
2551 }
2552 PrintRetainingPath(thread, obj: &obj, limit, js);
2553}
2554
2555static const MethodParameter* const get_retained_size_params[] = {
2556 RUNNABLE_ISOLATE_PARAMETER,
2557 new IdParameter("targetId", true),
2558 nullptr,
2559};
2560
2561static void GetRetainedSize(Thread* thread, JSONStream* js) {
2562 const char* target_id = js->LookupParam(key: "targetId");
2563 ASSERT(target_id != nullptr);
2564 ObjectIdRing::LookupResult lookup_result;
2565 Object& obj =
2566 Object::Handle(ptr: LookupHeapObject(thread, id_original: target_id, result: &lookup_result));
2567 if (obj.ptr() == Object::sentinel().ptr()) {
2568 if (lookup_result == ObjectIdRing::kCollected) {
2569 PrintSentinel(js, sentinel_type: kCollectedSentinel);
2570 } else if (lookup_result == ObjectIdRing::kExpired) {
2571 PrintSentinel(js, sentinel_type: kExpiredSentinel);
2572 } else {
2573 PrintInvalidParamError(js, param: "targetId");
2574 }
2575 return;
2576 }
2577 // TODO(rmacnak): There is no way to get the size retained by a class object.
2578 // SizeRetainedByClass should be a separate RPC.
2579 if (obj.IsClass()) {
2580 const Class& cls = Class::Cast(obj);
2581 ObjectGraph graph(thread);
2582 intptr_t retained_size = graph.SizeRetainedByClass(class_id: cls.id());
2583 const Object& result = Object::Handle(ptr: Integer::New(value: retained_size));
2584 result.PrintJSON(stream: js, ref: true);
2585 return;
2586 }
2587
2588 ObjectGraph graph(thread);
2589 intptr_t retained_size = graph.SizeRetainedByInstance(obj);
2590 const Object& result = Object::Handle(ptr: Integer::New(value: retained_size));
2591 result.PrintJSON(stream: js, ref: true);
2592}
2593
2594static const MethodParameter* const get_reachable_size_params[] = {
2595 RUNNABLE_ISOLATE_PARAMETER,
2596 new IdParameter("targetId", true),
2597 nullptr,
2598};
2599
2600static void GetReachableSize(Thread* thread, JSONStream* js) {
2601 const char* target_id = js->LookupParam(key: "targetId");
2602 ASSERT(target_id != nullptr);
2603 ObjectIdRing::LookupResult lookup_result;
2604 Object& obj =
2605 Object::Handle(ptr: LookupHeapObject(thread, id_original: target_id, result: &lookup_result));
2606 if (obj.ptr() == Object::sentinel().ptr()) {
2607 if (lookup_result == ObjectIdRing::kCollected) {
2608 PrintSentinel(js, sentinel_type: kCollectedSentinel);
2609 } else if (lookup_result == ObjectIdRing::kExpired) {
2610 PrintSentinel(js, sentinel_type: kExpiredSentinel);
2611 } else {
2612 PrintInvalidParamError(js, param: "targetId");
2613 }
2614 return;
2615 }
2616 // TODO(rmacnak): There is no way to get the size retained by a class object.
2617 // SizeRetainedByClass should be a separate RPC.
2618 if (obj.IsClass()) {
2619 const Class& cls = Class::Cast(obj);
2620 ObjectGraph graph(thread);
2621 intptr_t retained_size = graph.SizeReachableByClass(class_id: cls.id());
2622 const Object& result = Object::Handle(ptr: Integer::New(value: retained_size));
2623 result.PrintJSON(stream: js, ref: true);
2624 return;
2625 }
2626
2627 ObjectGraph graph(thread);
2628 intptr_t retained_size = graph.SizeReachableByInstance(obj);
2629 const Object& result = Object::Handle(ptr: Integer::New(value: retained_size));
2630 result.PrintJSON(stream: js, ref: true);
2631}
2632
2633static const MethodParameter* const invoke_params[] = {
2634 RUNNABLE_ISOLATE_PARAMETER,
2635 nullptr,
2636};
2637
2638static void Invoke(Thread* thread, JSONStream* js) {
2639 const char* receiver_id = js->LookupParam(key: "targetId");
2640 if (receiver_id == nullptr) {
2641 PrintMissingParamError(js, param: "targetId");
2642 return;
2643 }
2644 const char* selector_cstr = js->LookupParam(key: "selector");
2645 if (selector_cstr == nullptr) {
2646 PrintMissingParamError(js, param: "selector");
2647 return;
2648 }
2649 const char* argument_ids = js->LookupParam(key: "argumentIds");
2650 if (argument_ids == nullptr) {
2651 PrintMissingParamError(js, param: "argumentIds");
2652 return;
2653 }
2654
2655#if !defined(DART_PRECOMPILED_RUNTIME)
2656 bool disable_breakpoints =
2657 BoolParameter::Parse(value: js->LookupParam(key: "disableBreakpoints"), default_value: false);
2658 DisableBreakpointsScope db(thread->isolate()->debugger(),
2659 disable_breakpoints);
2660#endif
2661
2662 Zone* zone = thread->zone();
2663 ObjectIdRing::LookupResult lookup_result;
2664 Object& receiver = Object::Handle(
2665 zone, ptr: LookupHeapObject(thread, id_original: receiver_id, result: &lookup_result));
2666 if (receiver.ptr() == Object::sentinel().ptr()) {
2667 if (lookup_result == ObjectIdRing::kCollected) {
2668 PrintSentinel(js, sentinel_type: kCollectedSentinel);
2669 } else if (lookup_result == ObjectIdRing::kExpired) {
2670 PrintSentinel(js, sentinel_type: kExpiredSentinel);
2671 } else {
2672 PrintInvalidParamError(js, param: "targetId");
2673 }
2674 return;
2675 }
2676
2677 const GrowableObjectArray& growable_args =
2678 GrowableObjectArray::Handle(zone, ptr: GrowableObjectArray::New());
2679
2680 bool is_instance = (receiver.IsInstance() || receiver.IsNull()) &&
2681 !ContainsNonInstance(obj: receiver);
2682 if (is_instance) {
2683 growable_args.Add(value: receiver);
2684 }
2685
2686 intptr_t n = strlen(argument_ids);
2687 if ((n < 2) || (argument_ids[0] != '[') || (argument_ids[n - 1] != ']')) {
2688 PrintInvalidParamError(js, param: "argumentIds");
2689 return;
2690 }
2691 if (n > 2) {
2692 intptr_t start = 1;
2693 while (start < n) {
2694 intptr_t end = start;
2695 while ((argument_ids[end + 1] != ',') && (argument_ids[end + 1] != ']')) {
2696 end++;
2697 }
2698 if (end == start) {
2699 // Empty element.
2700 PrintInvalidParamError(js, param: "argumentIds");
2701 return;
2702 }
2703
2704 const char* argument_id =
2705 zone->MakeCopyOfStringN(str: &argument_ids[start], len: end - start + 1);
2706
2707 ObjectIdRing::LookupResult lookup_result;
2708 Object& argument = Object::Handle(
2709 zone, ptr: LookupHeapObject(thread, id_original: argument_id, result: &lookup_result));
2710 // Invoke only accepts Instance arguments.
2711 if (!(argument.IsInstance() || argument.IsNull()) ||
2712 ContainsNonInstance(obj: argument)) {
2713 PrintInvalidParamError(js, param: "argumentIds");
2714 return;
2715 }
2716 if (argument.ptr() == Object::sentinel().ptr()) {
2717 if (lookup_result == ObjectIdRing::kCollected) {
2718 PrintSentinel(js, sentinel_type: kCollectedSentinel);
2719 } else if (lookup_result == ObjectIdRing::kExpired) {
2720 PrintSentinel(js, sentinel_type: kExpiredSentinel);
2721 } else {
2722 PrintInvalidParamError(js, param: "argumentIds");
2723 }
2724 return;
2725 }
2726 growable_args.Add(value: argument);
2727
2728 start = end + 3;
2729 }
2730 }
2731
2732 const String& selector = String::Handle(zone, ptr: String::New(cstr: selector_cstr));
2733 const Array& args =
2734 Array::Handle(zone, ptr: Array::MakeFixedLength(growable_array: growable_args));
2735 const Array& arg_names = Object::empty_array();
2736
2737 if (receiver.IsLibrary()) {
2738 const Library& lib = Library::Cast(obj: receiver);
2739 const Object& result =
2740 Object::Handle(zone, ptr: lib.Invoke(selector, arguments: args, argument_names: arg_names));
2741 result.PrintJSON(stream: js, ref: true);
2742 return;
2743 }
2744 if (receiver.IsClass()) {
2745 const Class& cls = Class::Cast(obj: receiver);
2746 const Object& result =
2747 Object::Handle(zone, ptr: cls.Invoke(selector, arguments: args, argument_names: arg_names));
2748 result.PrintJSON(stream: js, ref: true);
2749 return;
2750 }
2751 if (is_instance) {
2752 // We don't use Instance::Cast here because it doesn't allow null.
2753 Instance& instance = Instance::Handle(zone);
2754 instance ^= receiver.ptr();
2755 const Object& result =
2756 Object::Handle(zone, ptr: instance.Invoke(selector, arguments: args, argument_names: arg_names));
2757 result.PrintJSON(stream: js, ref: true);
2758 return;
2759 }
2760 js->PrintError(code: kInvalidParams,
2761 details_format: "%s: invalid 'targetId' parameter: "
2762 "Cannot invoke against a VM-internal object",
2763 js->method());
2764}
2765
2766static const MethodParameter* const evaluate_params[] = {
2767 RUNNABLE_ISOLATE_PARAMETER,
2768 nullptr,
2769};
2770
2771static bool IsAlpha(char c) {
2772 return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
2773}
2774static bool IsAlphaOrDollar(char c) {
2775 return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '$');
2776}
2777static bool IsAlphaNum(char c) {
2778 return (c >= '0' && c <= '9') || IsAlpha(c);
2779}
2780static bool IsAlphaNumOrDollar(char c) {
2781 return (c >= '0' && c <= '9') || IsAlphaOrDollar(c);
2782}
2783static bool IsWhitespace(char c) {
2784 return c <= ' ';
2785}
2786static bool IsObjectIdChar(char c) {
2787 return IsAlphaNum(c) || c == '/' || c == '-' || c == '@' || c == '%';
2788}
2789
2790// TODO(vm-service): Consider whether we should pass structured objects in
2791// service messages instead of always flattening them to C strings.
2792static bool ParseScope(const char* scope,
2793 GrowableArray<const char*>* names,
2794 GrowableArray<const char*>* ids) {
2795 Zone* zone = Thread::Current()->zone();
2796 const char* c = scope;
2797 if (*c++ != '{') return false;
2798
2799 for (;;) {
2800 while (IsWhitespace(c: *c)) {
2801 c++;
2802 }
2803
2804 if (*c == '}') return true;
2805
2806 const char* name = c;
2807 if (!IsAlphaOrDollar(c: *c)) return false;
2808 while (IsAlphaNumOrDollar(c: *c)) {
2809 c++;
2810 }
2811 names->Add(zone->MakeCopyOfStringN(str: name, len: c - name));
2812
2813 while (IsWhitespace(c: *c)) {
2814 c++;
2815 }
2816
2817 if (*c++ != ':') return false;
2818
2819 while (IsWhitespace(c: *c)) {
2820 c++;
2821 }
2822
2823 const char* id = c;
2824 if (!IsObjectIdChar(c: *c)) return false;
2825 while (IsObjectIdChar(c: *c)) {
2826 c++;
2827 }
2828 ids->Add(zone->MakeCopyOfStringN(str: id, len: c - id));
2829
2830 while (IsWhitespace(c: *c)) {
2831 c++;
2832 }
2833 if (*c == ',') c++;
2834 }
2835
2836 return false;
2837}
2838
2839static bool BuildScope(Thread* thread,
2840 JSONStream* js,
2841 const GrowableObjectArray& names,
2842 const GrowableObjectArray& values) {
2843 const char* scope = js->LookupParam(key: "scope");
2844 GrowableArray<const char*> cnames;
2845 GrowableArray<const char*> cids;
2846 if (scope != nullptr) {
2847 if (!ParseScope(scope, names: &cnames, ids: &cids)) {
2848 PrintInvalidParamError(js, param: "scope");
2849 return true;
2850 }
2851 String& name = String::Handle();
2852 Object& obj = Object::Handle();
2853 for (intptr_t i = 0; i < cids.length(); i++) {
2854 ObjectIdRing::LookupResult lookup_result;
2855 obj = LookupHeapObject(thread, cids[i], &lookup_result);
2856 if (obj.ptr() == Object::sentinel().ptr()) {
2857 if (lookup_result == ObjectIdRing::kCollected) {
2858 PrintSentinel(js, sentinel_type: kCollectedSentinel);
2859 } else if (lookup_result == ObjectIdRing::kExpired) {
2860 PrintSentinel(js, sentinel_type: kExpiredSentinel);
2861 } else {
2862 PrintInvalidParamError(js, param: "targetId");
2863 }
2864 return true;
2865 }
2866 if ((!obj.IsInstance() && !obj.IsNull()) || ContainsNonInstance(obj)) {
2867 js->PrintError(code: kInvalidParams,
2868 details_format: "%s: invalid scope 'targetId' parameter: "
2869 "Cannot evaluate against a VM-internal object",
2870 js->method());
2871 return true;
2872 }
2873 name = String::New(cnames[i]);
2874 names.Add(value: name);
2875 values.Add(value: obj);
2876 }
2877 }
2878 return false;
2879}
2880
2881static void Evaluate(Thread* thread, JSONStream* js) {
2882 // If a compilation service is available, this RPC invocation will have been
2883 // intercepted by RunningIsolates.routeRequest.
2884 js->PrintError(
2885 code: kExpressionCompilationError,
2886 details_format: "%s: No compilation service available; cannot evaluate from source.",
2887 js->method());
2888}
2889
2890static const MethodParameter* const build_expression_evaluation_scope_params[] =
2891 {
2892 RUNNABLE_ISOLATE_PARAMETER,
2893 new IdParameter("frameIndex", false),
2894 new IdParameter("targetId", false),
2895 nullptr,
2896};
2897
2898static void CollectStringifiedType(Zone* zone,
2899 const AbstractType& type,
2900 const GrowableObjectArray& output) {
2901 Instance& instance = Instance::Handle(zone);
2902 if (type.IsFunctionType()) {
2903 // The closure class
2904 // (IsolateGroup::Current()->object_store()->closure_class())
2905 // is statically typed weird (the call method redirects to itself)
2906 // and the type is therefore not useful for the CFE. We use null instead.
2907 output.Add(value: instance);
2908 return;
2909 }
2910 if (type.IsRecordType()) {
2911 // _Record class is not useful for the CFE. We use null instead.
2912 output.Add(value: instance);
2913 return;
2914 }
2915 if (type.IsDynamicType()) {
2916 // Dynamic is weird in that it seems to have a class with no name and a
2917 // library called something like '7189777121420'. We use null instead.
2918 output.Add(value: instance);
2919 return;
2920 }
2921 if (type.IsTypeParameter()) {
2922 // Calling type_class on a type parameter will crash the VM.
2923 // We use null instead.
2924 output.Add(value: instance);
2925 return;
2926 }
2927 ASSERT(type.IsType());
2928
2929 const Class& cls = Class::Handle(ptr: type.type_class());
2930 const Library& lib = Library::Handle(zone, ptr: cls.library());
2931
2932 instance ^= lib.url();
2933 output.Add(value: instance);
2934
2935 instance ^= cls.ScrubbedName();
2936 output.Add(value: instance);
2937
2938 instance ^= Smi::New(value: (intptr_t)type.nullability());
2939 output.Add(value: instance);
2940
2941 const TypeArguments& srcArguments =
2942 TypeArguments::Handle(ptr: Type::Cast(obj: type).arguments());
2943 instance ^= Smi::New(value: srcArguments.Length());
2944 output.Add(value: instance);
2945 for (int i = 0; i < srcArguments.Length(); i++) {
2946 const AbstractType& src_type = AbstractType::Handle(ptr: srcArguments.TypeAt(index: i));
2947 CollectStringifiedType(zone, type: src_type, output);
2948 }
2949}
2950
2951static void BuildExpressionEvaluationScope(Thread* thread, JSONStream* js) {
2952 if (CheckDebuggerDisabled(thread, js)) {
2953 return;
2954 }
2955
2956 Isolate* isolate = thread->isolate();
2957 DebuggerStackTrace* stack = isolate->debugger()->StackTrace();
2958 intptr_t framePos = UIntParameter::Parse(value: js->LookupParam(key: "frameIndex"));
2959 if (framePos >= stack->Length()) {
2960 PrintInvalidParamError(js, param: "frameIndex");
2961 return;
2962 }
2963
2964 Zone* zone = thread->zone();
2965 const GrowableObjectArray& param_names =
2966 GrowableObjectArray::Handle(zone, ptr: GrowableObjectArray::New());
2967 const GrowableObjectArray& param_values =
2968 GrowableObjectArray::Handle(zone, ptr: GrowableObjectArray::New());
2969 const GrowableObjectArray& type_params_names =
2970 GrowableObjectArray::Handle(zone, ptr: GrowableObjectArray::New());
2971 const GrowableObjectArray& type_params_bounds =
2972 GrowableObjectArray::Handle(zone, ptr: GrowableObjectArray::New());
2973 const GrowableObjectArray& type_params_defaults =
2974 GrowableObjectArray::Handle(zone, ptr: GrowableObjectArray::New());
2975 String& klass_name = String::Handle(zone);
2976 String& method_name = String::Handle(zone);
2977 String& library_uri = String::Handle(zone);
2978 bool isStatic = false;
2979
2980 if (BuildScope(thread, js, names: param_names, values: param_values)) {
2981 return;
2982 }
2983
2984 if (js->HasParam(key: "frameIndex")) {
2985 // building scope in the context of a given frame
2986 DebuggerStackTrace* stack = isolate->debugger()->StackTrace();
2987 intptr_t framePos = UIntParameter::Parse(value: js->LookupParam(key: "frameIndex"));
2988 if (framePos >= stack->Length()) {
2989 PrintInvalidParamError(js, param: "frameIndex");
2990 return;
2991 }
2992
2993 ActivationFrame* frame = stack->FrameAt(i: framePos);
2994 frame->BuildParameters(param_names, param_values, type_params_names,
2995 type_params_bounds, type_params_defaults);
2996
2997 if (frame->function().is_static()) {
2998 const Class& cls = Class::Handle(zone, ptr: frame->function().Owner());
2999 if (!cls.IsTopLevel()) {
3000 klass_name = cls.UserVisibleName();
3001 }
3002 library_uri = Library::Handle(zone, ptr: cls.library()).url();
3003 method_name = frame->function().UserVisibleName();
3004 isStatic = true;
3005 } else {
3006 Class& method_cls = Class::Handle(zone, ptr: frame->function().Owner());
3007 method_cls = method_cls.Mixin();
3008 library_uri = Library::Handle(zone, ptr: method_cls.library()).url();
3009 klass_name = method_cls.UserVisibleName();
3010 method_name = frame->function().UserVisibleName();
3011 isStatic = false;
3012 }
3013 } else {
3014 // building scope in the context of a given object
3015 if (!js->HasParam(key: "targetId")) {
3016 js->PrintError(code: kInvalidParams,
3017 details_format: "Either targetId or frameIndex has to be provided.");
3018 return;
3019 }
3020 const char* target_id = js->LookupParam(key: "targetId");
3021
3022 ObjectIdRing::LookupResult lookup_result;
3023 Object& obj = Object::Handle(
3024 zone, ptr: LookupHeapObject(thread, id_original: target_id, result: &lookup_result));
3025 if (obj.ptr() == Object::sentinel().ptr()) {
3026 PrintInvalidParamError(js, param: "targetId");
3027 return;
3028 }
3029 if (obj.IsLibrary()) {
3030 const Library& lib = Library::Cast(obj);
3031 library_uri = lib.url();
3032 isStatic = true;
3033 } else if (obj.IsClass() || ((obj.IsInstance() || obj.IsNull()) &&
3034 !ContainsNonInstance(obj))) {
3035 Class& cls = Class::Handle(zone);
3036 if (obj.IsClass()) {
3037 cls ^= obj.ptr();
3038 isStatic = true;
3039 } else {
3040 Instance& instance = Instance::Handle(zone);
3041 instance ^= obj.ptr();
3042 cls = instance.clazz();
3043 cls = cls.Mixin();
3044 isStatic = false;
3045 }
3046 if (!cls.IsTopLevel() &&
3047 (IsInternalOnlyClassId(index: cls.id()) || cls.id() == kTypeArgumentsCid)) {
3048 js->PrintError(
3049 code: kInvalidParams,
3050 details_format: "Expressions can be evaluated only with regular Dart instances");
3051 return;
3052 }
3053
3054 if (!cls.IsTopLevel()) {
3055 klass_name = cls.UserVisibleName();
3056 }
3057 library_uri = Library::Handle(zone, ptr: cls.library()).url();
3058 } else {
3059 js->PrintError(code: kInvalidParams,
3060 details_format: "%s: invalid 'targetId' parameter: "
3061 "Cannot evaluate against a VM-internal object",
3062 js->method());
3063 return;
3064 }
3065 }
3066
3067 JSONObject report(js);
3068 {
3069 JSONArray jsonParamNames(&report, "param_names");
3070
3071 String& param_name = String::Handle(zone);
3072 for (intptr_t i = 0; i < param_names.Length(); i++) {
3073 param_name ^= param_names.At(index: i);
3074 jsonParamNames.AddValue(s: param_name.ToCString());
3075 }
3076 }
3077 {
3078 const JSONArray jsonParamTypes(&report, "param_types");
3079 Object& obj = Object::Handle();
3080 Instance& instance = Instance::Handle();
3081 const GrowableObjectArray& param_types =
3082 GrowableObjectArray::Handle(zone, ptr: GrowableObjectArray::New());
3083 AbstractType& type = AbstractType::Handle();
3084 for (intptr_t i = 0; i < param_names.Length(); i++) {
3085 obj = param_values.At(index: i);
3086 if (obj.IsNull()) {
3087 param_types.Add(value: obj);
3088 } else if (obj.IsInstance()) {
3089 instance ^= param_values.At(index: i);
3090 type = instance.GetType(space: Heap::kNew);
3091 CollectStringifiedType(zone, type, output: param_types);
3092 }
3093 }
3094 for (intptr_t i = 0; i < param_types.Length(); i++) {
3095 instance ^= param_types.At(index: i);
3096 jsonParamTypes.AddValue(s: instance.ToCString());
3097 }
3098 }
3099
3100 {
3101 JSONArray jsonTypeParamsNames(&report, "type_params_names");
3102 String& type_param_name = String::Handle(zone);
3103 for (intptr_t i = 0; i < type_params_names.Length(); i++) {
3104 type_param_name ^= type_params_names.At(index: i);
3105 jsonTypeParamsNames.AddValue(s: type_param_name.ToCString());
3106 }
3107 }
3108 {
3109 const JSONArray jsonParamTypes(&report, "type_params_bounds");
3110 const GrowableObjectArray& type_params_bounds_strings =
3111 GrowableObjectArray::Handle(zone, ptr: GrowableObjectArray::New());
3112 AbstractType& type = AbstractType::Handle();
3113 for (intptr_t i = 0; i < type_params_bounds.Length(); i++) {
3114 type ^= type_params_bounds.At(index: i);
3115 CollectStringifiedType(zone, type, output: type_params_bounds_strings);
3116 }
3117 Instance& instance = Instance::Handle();
3118 for (intptr_t i = 0; i < type_params_bounds_strings.Length(); i++) {
3119 instance ^= type_params_bounds_strings.At(index: i);
3120 jsonParamTypes.AddValue(s: instance.ToCString());
3121 }
3122 }
3123 {
3124 const JSONArray jsonParamTypes(&report, "type_params_defaults");
3125 const GrowableObjectArray& type_params_defaults_strings =
3126 GrowableObjectArray::Handle(zone, ptr: GrowableObjectArray::New());
3127 AbstractType& type = AbstractType::Handle();
3128 for (intptr_t i = 0; i < type_params_defaults.Length(); i++) {
3129 type ^= type_params_defaults.At(index: i);
3130 CollectStringifiedType(zone, type, output: type_params_defaults_strings);
3131 }
3132 Instance& instance = Instance::Handle();
3133 for (intptr_t i = 0; i < type_params_defaults_strings.Length(); i++) {
3134 instance ^= type_params_defaults_strings.At(index: i);
3135 jsonParamTypes.AddValue(s: instance.ToCString());
3136 }
3137 }
3138 report.AddProperty(name: "libraryUri", s: library_uri.ToCString());
3139 if (!klass_name.IsNull()) {
3140 report.AddProperty(name: "klass", s: klass_name.ToCString());
3141 }
3142 if (!method_name.IsNull()) {
3143 report.AddProperty(name: "method", s: method_name.ToCString());
3144 }
3145 report.AddProperty(name: "isStatic", b: isStatic);
3146}
3147
3148#if !defined(DART_PRECOMPILED_RUNTIME)
3149// Parse comma-separated list of values, put them into values
3150static bool ParseCSVList(const char* csv_list,
3151 const GrowableObjectArray& values) {
3152 Zone* zone = Thread::Current()->zone();
3153 String& s = String::Handle(zone);
3154 const char* c = csv_list;
3155 if (*c++ != '[') return false;
3156 while (IsWhitespace(c: *c) && *c != '\0') {
3157 c++;
3158 }
3159 while (*c != '\0') {
3160 const char* value = c;
3161 while (*c != '\0' && *c != ']' && *c != ',' && !IsWhitespace(c: *c)) {
3162 c++;
3163 }
3164 if (c > value) {
3165 s = String::New(cstr: zone->MakeCopyOfStringN(str: value, len: c - value));
3166 values.Add(value: s);
3167 }
3168 switch (*c) {
3169 case '\0':
3170 return false;
3171 case ',':
3172 c++;
3173 break;
3174 case ']':
3175 return true;
3176 }
3177 while (IsWhitespace(c: *c) && *c != '\0') {
3178 c++;
3179 }
3180 }
3181 return false;
3182}
3183#endif
3184
3185static const MethodParameter* const compile_expression_params[] = {
3186 RUNNABLE_ISOLATE_PARAMETER,
3187 new StringParameter("expression", true),
3188 new StringParameter("definitions", false),
3189 new StringParameter("definitionTypes", false),
3190 new StringParameter("typeDefinitions", false),
3191 new StringParameter("typeBounds", false),
3192 new StringParameter("typeDefaults", false),
3193 new StringParameter("libraryUri", true),
3194 new StringParameter("klass", false),
3195 new BoolParameter("isStatic", false),
3196 new StringParameter("method", false),
3197 nullptr,
3198};
3199
3200static void CompileExpression(Thread* thread, JSONStream* js) {
3201#if defined(DART_PRECOMPILED_RUNTIME)
3202 js->PrintError(kFeatureDisabled, "Debugger is disabled in AOT mode.");
3203#else
3204 if (CheckDebuggerDisabled(thread, js)) {
3205 return;
3206 }
3207
3208 if (!KernelIsolate::IsRunning() && !KernelIsolate::Start()) {
3209 js->PrintError(
3210 code: kExpressionCompilationError,
3211 details_format: "%s: No compilation service available; cannot evaluate from source.",
3212 js->method());
3213 return;
3214 }
3215
3216 const char* klass = js->LookupParam(key: "klass");
3217 bool is_static =
3218 BoolParameter::Parse(value: js->LookupParam(key: "isStatic"), default_value: (klass == nullptr));
3219
3220 const GrowableObjectArray& params =
3221 GrowableObjectArray::Handle(thread->zone(), GrowableObjectArray::New());
3222 if (!ParseCSVList(csv_list: js->LookupParam(key: "definitions"), values: params)) {
3223 PrintInvalidParamError(js, param: "definitions");
3224 return;
3225 }
3226 const GrowableObjectArray& param_types =
3227 GrowableObjectArray::Handle(thread->zone(), GrowableObjectArray::New());
3228 if (!ParseCSVList(csv_list: js->LookupParam(key: "definitionTypes"), values: param_types)) {
3229 PrintInvalidParamError(js, param: "definitionTypes");
3230 return;
3231 }
3232
3233 const GrowableObjectArray& type_params =
3234 GrowableObjectArray::Handle(thread->zone(), GrowableObjectArray::New());
3235 if (!ParseCSVList(csv_list: js->LookupParam(key: "typeDefinitions"), values: type_params)) {
3236 PrintInvalidParamError(js, param: "typedDefinitions");
3237 return;
3238 }
3239 const GrowableObjectArray& type_bounds =
3240 GrowableObjectArray::Handle(thread->zone(), GrowableObjectArray::New());
3241 if (!ParseCSVList(csv_list: js->LookupParam(key: "typeBounds"), values: type_bounds)) {
3242 PrintInvalidParamError(js, param: "typeBounds");
3243 return;
3244 }
3245 const GrowableObjectArray& type_defaults =
3246 GrowableObjectArray::Handle(thread->zone(), GrowableObjectArray::New());
3247 if (!ParseCSVList(csv_list: js->LookupParam(key: "typeDefaults"), values: type_defaults)) {
3248 PrintInvalidParamError(js, param: "typeDefaults");
3249 return;
3250 }
3251
3252 const uint8_t* kernel_buffer = Service::dart_library_kernel();
3253 const intptr_t kernel_buffer_len = Service::dart_library_kernel_length();
3254
3255 Dart_KernelCompilationResult compilation_result =
3256 KernelIsolate::CompileExpressionToKernel(
3257 platform_kernel: kernel_buffer, platform_kernel_size: kernel_buffer_len, expression: js->LookupParam(key: "expression"),
3258 definitions: Array::Handle(ptr: Array::MakeFixedLength(growable_array: params)),
3259 definition_types: Array::Handle(ptr: Array::MakeFixedLength(growable_array: param_types)),
3260 type_definitions: Array::Handle(ptr: Array::MakeFixedLength(growable_array: type_params)),
3261 type_bounds: Array::Handle(ptr: Array::MakeFixedLength(growable_array: type_bounds)),
3262 type_defaults: Array::Handle(ptr: Array::MakeFixedLength(growable_array: type_defaults)),
3263 library_url: js->LookupParam(key: "libraryUri"), klass: js->LookupParam(key: "klass"),
3264 method: js->LookupParam(key: "method"), is_static);
3265
3266 if (compilation_result.status != Dart_KernelCompilationStatus_Ok) {
3267 js->PrintError(code: kExpressionCompilationError, details_format: "%s", compilation_result.error);
3268 free(compilation_result.error);
3269 return;
3270 }
3271
3272 const uint8_t* kernel_bytes = compilation_result.kernel;
3273 intptr_t kernel_length = compilation_result.kernel_size;
3274 ASSERT(kernel_bytes != nullptr);
3275
3276 JSONObject report(js);
3277 report.AddPropertyBase64(name: "kernelBytes", bytes: kernel_bytes, length: kernel_length);
3278#endif
3279}
3280
3281static const MethodParameter* const evaluate_compiled_expression_params[] = {
3282 RUNNABLE_ISOLATE_PARAMETER,
3283 new UIntParameter("frameIndex", false),
3284 new IdParameter("targetId", false),
3285 new StringParameter("kernelBytes", true),
3286 nullptr,
3287};
3288
3289ExternalTypedDataPtr DecodeKernelBuffer(const char* kernel_buffer_base64) {
3290 intptr_t kernel_length;
3291 uint8_t* kernel_buffer = DecodeBase64(str: kernel_buffer_base64, out_decoded_len: &kernel_length);
3292 return ExternalTypedData::NewFinalizeWithFree(data: kernel_buffer, len: kernel_length);
3293}
3294
3295static void EvaluateCompiledExpression(Thread* thread, JSONStream* js) {
3296 if (CheckDebuggerDisabled(thread, js)) {
3297 return;
3298 }
3299
3300 Isolate* isolate = thread->isolate();
3301
3302 bool disable_breakpoints =
3303 BoolParameter::Parse(value: js->LookupParam(key: "disableBreakpoints"), default_value: false);
3304 DisableBreakpointsScope db(isolate->debugger(), disable_breakpoints);
3305
3306 DebuggerStackTrace* stack = isolate->debugger()->StackTrace();
3307 intptr_t frame_pos = UIntParameter::Parse(value: js->LookupParam(key: "frameIndex"));
3308 if (frame_pos >= stack->Length()) {
3309 PrintInvalidParamError(js, param: "frameIndex");
3310 return;
3311 }
3312 Zone* zone = thread->zone();
3313 const GrowableObjectArray& param_names =
3314 GrowableObjectArray::Handle(zone, ptr: GrowableObjectArray::New());
3315 const GrowableObjectArray& param_values =
3316 GrowableObjectArray::Handle(zone, ptr: GrowableObjectArray::New());
3317 if (BuildScope(thread, js, names: param_names, values: param_values)) {
3318 return;
3319 }
3320 const GrowableObjectArray& type_params_names =
3321 GrowableObjectArray::Handle(zone, ptr: GrowableObjectArray::New());
3322 const GrowableObjectArray& type_params_bounds =
3323 GrowableObjectArray::Handle(zone, ptr: GrowableObjectArray::New());
3324 const GrowableObjectArray& type_params_defaults =
3325 GrowableObjectArray::Handle(zone, ptr: GrowableObjectArray::New());
3326
3327 const ExternalTypedData& kernel_data = ExternalTypedData::Handle(
3328 zone, ptr: DecodeKernelBuffer(kernel_buffer_base64: js->LookupParam(key: "kernelBytes")));
3329
3330 if (js->HasParam(key: "frameIndex")) {
3331 DebuggerStackTrace* stack = isolate->debugger()->StackTrace();
3332 intptr_t frame_pos = UIntParameter::Parse(value: js->LookupParam(key: "frameIndex"));
3333 if (frame_pos >= stack->Length()) {
3334 PrintInvalidParamError(js, param: "frameIndex");
3335 return;
3336 }
3337
3338 ActivationFrame* frame = stack->FrameAt(i: frame_pos);
3339 TypeArguments& type_arguments = TypeArguments::Handle(
3340 zone,
3341 ptr: frame->BuildParameters(param_names, param_values, type_params_names,
3342 type_params_bounds, type_params_defaults));
3343
3344 const Object& result = Object::Handle(
3345 zone,
3346 ptr: frame->EvaluateCompiledExpression(
3347 kernel_data,
3348 arguments: Array::Handle(zone, ptr: Array::MakeFixedLength(growable_array: type_params_names)),
3349 type_definitions: Array::Handle(zone, ptr: Array::MakeFixedLength(growable_array: param_values)),
3350 type_arguments));
3351 result.PrintJSON(stream: js, ref: true);
3352 } else {
3353 // evaluating expression in the context of a given object
3354 if (!js->HasParam(key: "targetId")) {
3355 js->PrintError(code: kInvalidParams,
3356 details_format: "Either targetId or frameIndex has to be provided.");
3357 return;
3358 }
3359 const char* target_id = js->LookupParam(key: "targetId");
3360 ObjectIdRing::LookupResult lookup_result;
3361 Object& obj = Object::Handle(
3362 zone, ptr: LookupHeapObject(thread, id_original: target_id, result: &lookup_result));
3363 if (obj.ptr() == Object::sentinel().ptr()) {
3364 if (lookup_result == ObjectIdRing::kCollected) {
3365 PrintSentinel(js, sentinel_type: kCollectedSentinel);
3366 } else if (lookup_result == ObjectIdRing::kExpired) {
3367 PrintSentinel(js, sentinel_type: kExpiredSentinel);
3368 } else {
3369 PrintInvalidParamError(js, param: "targetId");
3370 }
3371 return;
3372 }
3373 const auto& type_params_names_fixed =
3374 Array::Handle(zone, ptr: Array::MakeFixedLength(growable_array: type_params_names));
3375 const auto& param_values_fixed =
3376 Array::Handle(zone, ptr: Array::MakeFixedLength(growable_array: param_values));
3377
3378 TypeArguments& type_arguments = TypeArguments::Handle(zone);
3379 if (obj.IsLibrary()) {
3380 const auto& lib = Library::Cast(obj);
3381 const auto& result = Object::Handle(
3382 zone,
3383 lib.EvaluateCompiledExpression(kernel_data, type_params_names_fixed,
3384 param_values_fixed, type_arguments));
3385 result.PrintJSON(js, true);
3386 return;
3387 }
3388 if (obj.IsClass()) {
3389 const auto& cls = Class::Cast(obj);
3390 const auto& result = Object::Handle(
3391 zone,
3392 cls.EvaluateCompiledExpression(kernel_data, type_params_names_fixed,
3393 param_values_fixed, type_arguments));
3394 result.PrintJSON(js, true);
3395 return;
3396 }
3397 if ((obj.IsInstance() || obj.IsNull()) && !ContainsNonInstance(obj)) {
3398 const auto& instance =
3399 Instance::Handle(zone, ptr: Instance::RawCast(raw: obj.ptr()));
3400 const auto& receiver_cls = Class::Handle(zone, instance.clazz());
3401 const auto& result = Object::Handle(
3402 zone, instance.EvaluateCompiledExpression(
3403 receiver_cls, kernel_data, type_params_names_fixed,
3404 param_values_fixed, type_arguments));
3405 result.PrintJSON(js, true);
3406 return;
3407 }
3408 js->PrintError(code: kInvalidParams,
3409 details_format: "%s: invalid 'targetId' parameter: "
3410 "Cannot evaluate against a VM-internal object",
3411 js->method());
3412 }
3413}
3414
3415static const MethodParameter* const evaluate_in_frame_params[] = {
3416 RUNNABLE_ISOLATE_PARAMETER,
3417 new UIntParameter("frameIndex", true),
3418 new MethodParameter("expression", true),
3419 nullptr,
3420};
3421
3422static void EvaluateInFrame(Thread* thread, JSONStream* js) {
3423 // If a compilation service is available, this RPC invocation will have been
3424 // intercepted by RunningIsolates.routeRequest.
3425 js->PrintError(
3426 code: kExpressionCompilationError,
3427 details_format: "%s: No compilation service available; cannot evaluate from source.",
3428 js->method());
3429}
3430
3431static void MarkClasses(const Class& root,
3432 bool include_subclasses,
3433 bool include_implementors) {
3434 Thread* thread = Thread::Current();
3435 HANDLESCOPE(thread);
3436 ClassTable* table = thread->isolate()->group()->class_table();
3437 GrowableArray<const Class*> worklist;
3438 table->SetCollectInstancesFor(cid: root.id(), trace: true);
3439 worklist.Add(&root);
3440 GrowableObjectArray& subclasses = GrowableObjectArray::Handle();
3441 GrowableObjectArray& implementors = GrowableObjectArray::Handle();
3442 while (!worklist.is_empty()) {
3443 const Class& cls = *worklist.RemoveLast();
3444 // All subclasses are implementors, but they are not included in
3445 // `direct_implementors`.
3446 if (include_subclasses || include_implementors) {
3447 subclasses = cls.direct_subclasses_unsafe();
3448 if (!subclasses.IsNull()) {
3449 for (intptr_t j = 0; j < subclasses.Length(); j++) {
3450 Class& subclass = Class::Handle();
3451 subclass ^= subclasses.At(index: j);
3452 if (!table->CollectInstancesFor(cid: subclass.id())) {
3453 table->SetCollectInstancesFor(cid: subclass.id(), trace: true);
3454 worklist.Add(&subclass);
3455 }
3456 }
3457 }
3458 }
3459 if (include_implementors) {
3460 implementors = cls.direct_implementors_unsafe();
3461 if (!implementors.IsNull()) {
3462 for (intptr_t j = 0; j < implementors.Length(); j++) {
3463 Class& implementor = Class::Handle();
3464 implementor ^= implementors.At(index: j);
3465 if (!table->CollectInstancesFor(cid: implementor.id())) {
3466 table->SetCollectInstancesFor(cid: implementor.id(), trace: true);
3467 worklist.Add(&implementor);
3468 }
3469 }
3470 }
3471 }
3472 }
3473}
3474
3475static void UnmarkClasses() {
3476 ClassTable* table = IsolateGroup::Current()->class_table();
3477 for (intptr_t i = 1; i < table->NumCids(); i++) {
3478 table->SetCollectInstancesFor(cid: i, trace: false);
3479 }
3480}
3481
3482class GetInstancesVisitor : public ObjectGraph::Visitor {
3483 public:
3484 GetInstancesVisitor(ZoneGrowableHandlePtrArray<Object>* storage,
3485 intptr_t limit)
3486 : table_(IsolateGroup::Current()->class_table()),
3487 storage_(storage),
3488 limit_(limit),
3489 count_(0) {}
3490
3491 virtual Direction VisitObject(ObjectGraph::StackIterator* it) {
3492 ObjectPtr raw_obj = it->Get();
3493 if (raw_obj->IsPseudoObject()) {
3494 return kProceed;
3495 }
3496 if (table_->CollectInstancesFor(cid: raw_obj->GetClassId())) {
3497 if (count_ < limit_) {
3498 storage_->Add(Object::Handle(ptr: raw_obj));
3499 }
3500 ++count_;
3501 }
3502 return kProceed;
3503 }
3504
3505 intptr_t count() const { return count_; }
3506
3507 private:
3508 ClassTable* const table_;
3509 ZoneGrowableHandlePtrArray<Object>* storage_;
3510 const intptr_t limit_;
3511 intptr_t count_;
3512};
3513
3514static const MethodParameter* const get_instances_params[] = {
3515 RUNNABLE_ISOLATE_PARAMETER,
3516 new IdParameter("objectId", /*required=*/true),
3517 new UIntParameter("limit", /*required=*/true),
3518 new BoolParameter("includeSubclasses", /*required=*/false),
3519 new BoolParameter("includeImplementers", /*required=*/false),
3520 nullptr,
3521};
3522
3523static void GetInstances(Thread* thread, JSONStream* js) {
3524 const char* object_id = js->LookupParam(key: "objectId");
3525 const intptr_t limit = UIntParameter::Parse(value: js->LookupParam(key: "limit"));
3526 const bool include_subclasses =
3527 BoolParameter::Parse(value: js->LookupParam(key: "includeSubclasses"), default_value: false);
3528 const bool include_implementers =
3529 BoolParameter::Parse(value: js->LookupParam(key: "includeImplementers"), default_value: false);
3530
3531 const Object& obj =
3532 Object::Handle(ptr: LookupHeapObject(thread, id_original: object_id, result: nullptr));
3533 if (obj.ptr() == Object::sentinel().ptr() || !obj.IsClass()) {
3534 PrintInvalidParamError(js, param: "objectId");
3535 return;
3536 }
3537 const Class& cls = Class::Cast(obj);
3538
3539 // Ensure the array and handles created below are promptly destroyed.
3540 StackZone zone(thread);
3541
3542 ZoneGrowableHandlePtrArray<Object> storage(thread->zone(), limit);
3543 GetInstancesVisitor visitor(&storage, limit);
3544 {
3545 ObjectGraph graph(thread);
3546 HeapIterationScope iteration_scope(Thread::Current(), true);
3547 MarkClasses(root: cls, include_subclasses, include_implementors: include_implementers);
3548 graph.IterateObjects(visitor: &visitor);
3549 UnmarkClasses();
3550 }
3551 intptr_t count = visitor.count();
3552 JSONObject jsobj(js);
3553 jsobj.AddProperty(name: "type", s: "InstanceSet");
3554 jsobj.AddProperty(name: "totalCount", i: count);
3555 {
3556 JSONArray samples(&jsobj, "instances");
3557 for (int i = 0; (i < limit) && (i < count); i++) {
3558 samples.AddValue(storage.At(i));
3559 }
3560 }
3561}
3562
3563static const MethodParameter* const get_instances_as_list_params[] = {
3564 RUNNABLE_ISOLATE_PARAMETER,
3565 new IdParameter("objectId", /*required=*/true),
3566 new BoolParameter("includeSubclasses", /*required=*/false),
3567 new BoolParameter("includeImplementers", /*required=*/false),
3568 nullptr,
3569};
3570
3571static void GetInstancesAsList(Thread* thread, JSONStream* js) {
3572 const char* object_id = js->LookupParam(key: "objectId");
3573 bool include_subclasses =
3574 BoolParameter::Parse(value: js->LookupParam(key: "includeSubclasses"), default_value: false);
3575 bool include_implementers =
3576 BoolParameter::Parse(value: js->LookupParam(key: "includeImplementers"), default_value: false);
3577
3578 const Object& obj =
3579 Object::Handle(ptr: LookupHeapObject(thread, id_original: object_id, result: nullptr));
3580 if (obj.ptr() == Object::sentinel().ptr() || !obj.IsClass()) {
3581 PrintInvalidParamError(js, param: "objectId");
3582 return;
3583 }
3584 const Class& cls = Class::Cast(obj);
3585
3586 Array& instances = Array::Handle();
3587 {
3588 // Ensure the |ZoneGrowableHandlePtrArray| and handles created below are
3589 // promptly destroyed.
3590 StackZone zone(thread);
3591
3592 ZoneGrowableHandlePtrArray<Object> storage(thread->zone(), 1024);
3593 GetInstancesVisitor visitor(&storage, kSmiMax);
3594 {
3595 ObjectGraph graph(thread);
3596 HeapIterationScope iteration_scope(Thread::Current(), true);
3597 MarkClasses(root: cls, include_subclasses, include_implementors: include_implementers);
3598 graph.IterateObjects(visitor: &visitor);
3599 UnmarkClasses();
3600 }
3601 intptr_t count = visitor.count();
3602 instances = Array::New(len: count);
3603 for (intptr_t i = 0; i < count; i++) {
3604 instances.SetAt(i, storage.At(i));
3605 }
3606 }
3607 instances.PrintJSON(stream: js, /*ref=*/true);
3608}
3609
3610static intptr_t ParseJSONArray(Thread* thread,
3611 const char* str,
3612 const GrowableObjectArray& elements) {
3613 ASSERT(str != nullptr);
3614 ASSERT(thread != nullptr);
3615 Zone* zone = thread->zone();
3616 intptr_t n = strlen(str);
3617 if (n < 2) {
3618 return -1;
3619 }
3620 intptr_t start = 1;
3621 while (start < n) {
3622 intptr_t end = start;
3623 while ((str[end + 1] != ',') && (str[end + 1] != ']')) {
3624 end++;
3625 }
3626 if (end == start) {
3627 // Empty element
3628 break;
3629 }
3630 String& element = String::Handle(
3631 zone, ptr: String::FromUTF8(utf8_array: reinterpret_cast<const uint8_t*>(&str[start]),
3632 array_len: end - start + 1));
3633 elements.Add(value: element);
3634 start = end + 3;
3635 }
3636 return 0;
3637}
3638
3639static const MethodParameter* const get_ports_params[] = {
3640 RUNNABLE_ISOLATE_PARAMETER,
3641 nullptr,
3642};
3643
3644static void GetPorts(Thread* thread, JSONStream* js) {
3645 // Ensure the array and handles created below are promptly destroyed.
3646 StackZone zone(thread);
3647 const GrowableObjectArray& ports = GrowableObjectArray::Handle(
3648 ptr: GrowableObjectArray::RawCast(raw: DartLibraryCalls::LookupOpenPorts()));
3649 JSONObject jsobj(js);
3650 jsobj.AddProperty(name: "type", s: "PortList");
3651 {
3652 ReceivePort& port = ReceivePort::Handle(zone: zone.GetZone());
3653 JSONArray arr(&jsobj, "ports");
3654 for (int i = 0; i < ports.Length(); ++i) {
3655 port ^= ports.At(index: i);
3656 ASSERT(port.is_open());
3657 if (port.keep_isolate_alive()) {
3658 arr.AddValue(obj: port);
3659 }
3660 }
3661 }
3662}
3663
3664#if !defined(DART_PRECOMPILED_RUNTIME)
3665static const char* const report_enum_names[] = {
3666 SourceReport::kCallSitesStr, SourceReport::kCoverageStr,
3667 SourceReport::kPossibleBreakpointsStr, SourceReport::kProfileStr,
3668 SourceReport::kBranchCoverageStr, nullptr,
3669};
3670#endif
3671
3672static const MethodParameter* const get_source_report_params[] = {
3673#if !defined(DART_PRECOMPILED_RUNTIME)
3674 RUNNABLE_ISOLATE_PARAMETER,
3675 new EnumListParameter("reports", true, report_enum_names),
3676 new IdParameter("scriptId", false),
3677 new UIntParameter("tokenPos", false),
3678 new UIntParameter("endTokenPos", false),
3679 new BoolParameter("forceCompile", false),
3680#endif
3681 nullptr,
3682};
3683
3684static void GetSourceReport(Thread* thread, JSONStream* js) {
3685#if defined(DART_PRECOMPILED_RUNTIME)
3686 js->PrintError(kFeatureDisabled, "disabled in AOT mode and PRODUCT.");
3687#else
3688 if (CheckCompilerDisabled(thread, js)) {
3689 return;
3690 }
3691
3692 char* reports_str = Utils::StrDup(s: js->LookupParam(key: "reports"));
3693 const EnumListParameter* reports_parameter =
3694 static_cast<const EnumListParameter*>(get_source_report_params[1]);
3695 const char** reports = reports_parameter->Parse(value: reports_str);
3696 const char** riter = reports;
3697 intptr_t report_set = 0;
3698 while (*riter != nullptr) {
3699 if (strcmp(*riter, SourceReport::kCallSitesStr) == 0) {
3700 report_set |= SourceReport::kCallSites;
3701 } else if (strcmp(*riter, SourceReport::kCoverageStr) == 0) {
3702 report_set |= SourceReport::kCoverage;
3703 } else if (strcmp(*riter, SourceReport::kPossibleBreakpointsStr) == 0) {
3704 report_set |= SourceReport::kPossibleBreakpoints;
3705 } else if (strcmp(*riter, SourceReport::kProfileStr) == 0) {
3706 report_set |= SourceReport::kProfile;
3707 } else if (strcmp(*riter, SourceReport::kBranchCoverageStr) == 0) {
3708 report_set |= SourceReport::kBranchCoverage;
3709 }
3710 riter++;
3711 }
3712 if (reports != nullptr) {
3713 delete[] reports;
3714 }
3715 free(reports_str);
3716
3717 SourceReport::CompileMode compile_mode = SourceReport::kNoCompile;
3718 if (BoolParameter::Parse(value: js->LookupParam(key: "forceCompile"), default_value: false)) {
3719 compile_mode = SourceReport::kForceCompile;
3720 }
3721
3722 bool report_lines =
3723 BoolParameter::Parse(value: js->LookupParam(key: "reportLines"), default_value: false);
3724
3725 Script& script = Script::Handle();
3726 intptr_t start_pos = UIntParameter::Parse(value: js->LookupParam(key: "tokenPos"));
3727 intptr_t end_pos = UIntParameter::Parse(value: js->LookupParam(key: "endTokenPos"));
3728
3729 if (js->HasParam(key: "scriptId")) {
3730 // Get the target script.
3731 const char* script_id_param = js->LookupParam(key: "scriptId");
3732 const Object& obj =
3733 Object::Handle(ptr: LookupHeapObject(thread, id_original: script_id_param, result: nullptr));
3734 if (obj.ptr() == Object::sentinel().ptr() || !obj.IsScript()) {
3735 PrintInvalidParamError(js, param: "scriptId");
3736 return;
3737 }
3738 script ^= obj.ptr();
3739 } else {
3740 if (js->HasParam(key: "tokenPos")) {
3741 js->PrintError(
3742 code: kInvalidParams,
3743 details_format: "%s: the 'tokenPos' parameter requires the 'scriptId' parameter",
3744 js->method());
3745 return;
3746 }
3747 if (js->HasParam(key: "endTokenPos")) {
3748 js->PrintError(
3749 code: kInvalidParams,
3750 details_format: "%s: the 'endTokenPos' parameter requires the 'scriptId' parameter",
3751 js->method());
3752 return;
3753 }
3754 }
3755
3756 const char* library_filters_param = js->LookupParam(key: "libraryFilters");
3757 GrowableObjectArray& library_filters = GrowableObjectArray::Handle();
3758 if (library_filters_param != nullptr) {
3759 library_filters = GrowableObjectArray::New();
3760 intptr_t library_filters_length =
3761 ParseJSONArray(thread, str: library_filters_param, elements: library_filters);
3762 if (library_filters_length < 0) {
3763 PrintInvalidParamError(js, param: "library_filters");
3764 return;
3765 }
3766 }
3767
3768 SourceReport report(report_set, library_filters, compile_mode, report_lines);
3769 report.PrintJSON(js, script, start_pos: TokenPosition::Deserialize(value: start_pos),
3770 end_pos: TokenPosition::Deserialize(value: end_pos));
3771#endif // !DART_PRECOMPILED_RUNTIME
3772}
3773
3774static const MethodParameter* const reload_sources_params[] = {
3775 RUNNABLE_ISOLATE_PARAMETER,
3776 new BoolParameter("force", false),
3777 new BoolParameter("pause", false),
3778 new StringParameter("rootLibUri", false),
3779 new StringParameter("packagesUri", false),
3780 nullptr,
3781};
3782
3783static void ReloadSources(Thread* thread, JSONStream* js) {
3784#if defined(DART_PRECOMPILED_RUNTIME)
3785 js->PrintError(kFeatureDisabled, "Compiler is disabled in AOT mode.");
3786#else
3787 if (CheckCompilerDisabled(thread, js)) {
3788 return;
3789 }
3790
3791 IsolateGroup* isolate_group = thread->isolate_group();
3792 if (isolate_group->library_tag_handler() == nullptr) {
3793 js->PrintError(code: kFeatureDisabled,
3794 details_format: "A library tag handler must be installed.");
3795 return;
3796 }
3797 // TODO(dartbug.com/36097): We need to change the "reloadSources" service-api
3798 // call to accept an isolate group instead of an isolate.
3799 Isolate* isolate = thread->isolate();
3800 if ((isolate->sticky_error() != Error::null()) ||
3801 (Thread::Current()->sticky_error() != Error::null())) {
3802 js->PrintError(code: kIsolateReloadBarred,
3803 details_format: "This isolate cannot reload sources anymore because there "
3804 "was an unhandled exception error. Restart the isolate.");
3805 return;
3806 }
3807 if (isolate_group->IsReloading()) {
3808 js->PrintError(code: kIsolateIsReloading, details_format: "This isolate is being reloaded.");
3809 return;
3810 }
3811 if (!isolate_group->CanReload()) {
3812 js->PrintError(code: kFeatureDisabled,
3813 details_format: "This isolate cannot reload sources right now.");
3814 return;
3815 }
3816 const bool force_reload =
3817 BoolParameter::Parse(value: js->LookupParam(key: "force"), default_value: false);
3818
3819 isolate_group->ReloadSources(js, force_reload, root_script_url: js->LookupParam(key: "rootLibUri"),
3820 packages_url: js->LookupParam(key: "packagesUri"));
3821
3822 Service::CheckForPause(isolate, stream: js);
3823
3824#endif
3825}
3826
3827void Service::CheckForPause(Isolate* isolate, JSONStream* stream) {
3828 // Should we pause?
3829 isolate->set_should_pause_post_service_request(
3830 BoolParameter::Parse(value: stream->LookupParam(key: "pause"), default_value: false));
3831}
3832
3833ErrorPtr Service::MaybePause(Isolate* isolate, const Error& error) {
3834 // Don't pause twice.
3835 if (!isolate->IsPaused()) {
3836 if (isolate->should_pause_post_service_request()) {
3837 isolate->set_should_pause_post_service_request(false);
3838 if (!error.IsNull()) {
3839 // Before pausing, restore the sticky error. The debugger will return it
3840 // from PausePostRequest.
3841 Thread::Current()->set_sticky_error(error);
3842 }
3843 return isolate->PausePostRequest();
3844 }
3845 }
3846 return error.ptr();
3847}
3848
3849static void AddBreakpointCommon(Thread* thread,
3850 JSONStream* js,
3851 const String& script_uri) {
3852 if (CheckDebuggerDisabled(thread, js)) {
3853 return;
3854 }
3855
3856 const char* line_param = js->LookupParam(key: "line");
3857 intptr_t line = UIntParameter::Parse(value: line_param);
3858 const char* col_param = js->LookupParam(key: "column");
3859 intptr_t col = -1;
3860 if (col_param != nullptr) {
3861 col = UIntParameter::Parse(value: col_param);
3862 if (col == 0) {
3863 // Column number is 1-based.
3864 PrintInvalidParamError(js, param: "column");
3865 return;
3866 }
3867 }
3868 ASSERT(!script_uri.IsNull());
3869 Breakpoint* bpt = nullptr;
3870 bpt = thread->isolate()->debugger()->SetBreakpointAtLineCol(script_url: script_uri, line_number: line,
3871 column_number: col);
3872 if (bpt == nullptr) {
3873 js->PrintError(code: kCannotAddBreakpoint,
3874 details_format: "%s: Cannot add breakpoint at line '%s'", js->method(),
3875 line_param);
3876 return;
3877 }
3878 bpt->PrintJSON(stream: js);
3879}
3880
3881static const MethodParameter* const add_breakpoint_params[] = {
3882 RUNNABLE_ISOLATE_PARAMETER,
3883 new IdParameter("scriptId", true),
3884 new UIntParameter("line", true),
3885 new UIntParameter("column", false),
3886 nullptr,
3887};
3888
3889static void AddBreakpoint(Thread* thread, JSONStream* js) {
3890 if (CheckDebuggerDisabled(thread, js)) {
3891 return;
3892 }
3893
3894 const char* script_id_param = js->LookupParam(key: "scriptId");
3895 Object& obj =
3896 Object::Handle(ptr: LookupHeapObject(thread, id_original: script_id_param, result: nullptr));
3897 if (obj.ptr() == Object::sentinel().ptr() || !obj.IsScript()) {
3898 PrintInvalidParamError(js, param: "scriptId");
3899 return;
3900 }
3901 const Script& script = Script::Cast(obj);
3902 const String& script_uri = String::Handle(ptr: script.url());
3903 ASSERT(!script_uri.IsNull());
3904 AddBreakpointCommon(thread, js, script_uri);
3905}
3906
3907static const MethodParameter* const add_breakpoint_with_script_uri_params[] = {
3908 RUNNABLE_ISOLATE_PARAMETER,
3909 new IdParameter("scriptUri", true),
3910 new UIntParameter("line", true),
3911 new UIntParameter("column", false),
3912 nullptr,
3913};
3914
3915static void AddBreakpointWithScriptUri(Thread* thread, JSONStream* js) {
3916 if (CheckDebuggerDisabled(thread, js)) {
3917 return;
3918 }
3919
3920 const char* script_uri_param = js->LookupParam(key: "scriptUri");
3921 const String& script_uri = String::Handle(ptr: String::New(cstr: script_uri_param));
3922 AddBreakpointCommon(thread, js, script_uri);
3923}
3924
3925static const MethodParameter* const add_breakpoint_at_entry_params[] = {
3926 RUNNABLE_ISOLATE_PARAMETER,
3927 new IdParameter("functionId", true),
3928 nullptr,
3929};
3930
3931static void AddBreakpointAtEntry(Thread* thread, JSONStream* js) {
3932 if (CheckDebuggerDisabled(thread, js)) {
3933 return;
3934 }
3935
3936 const char* function_id = js->LookupParam(key: "functionId");
3937 Object& obj = Object::Handle(ptr: LookupHeapObject(thread, id_original: function_id, result: nullptr));
3938 if (obj.ptr() == Object::sentinel().ptr() || !obj.IsFunction()) {
3939 PrintInvalidParamError(js, param: "functionId");
3940 return;
3941 }
3942 const Function& function = Function::Cast(obj);
3943 Breakpoint* bpt =
3944 thread->isolate()->debugger()->SetBreakpointAtEntry(target_function: function, single_shot: false);
3945 if (bpt == nullptr) {
3946 js->PrintError(code: kCannotAddBreakpoint,
3947 details_format: "%s: Cannot add breakpoint at function '%s'", js->method(),
3948 function.ToCString());
3949 return;
3950 }
3951 bpt->PrintJSON(stream: js);
3952}
3953
3954static const MethodParameter* const add_breakpoint_at_activation_params[] = {
3955 RUNNABLE_ISOLATE_PARAMETER,
3956 new IdParameter("objectId", true),
3957 nullptr,
3958};
3959
3960static void AddBreakpointAtActivation(Thread* thread, JSONStream* js) {
3961 if (CheckDebuggerDisabled(thread, js)) {
3962 return;
3963 }
3964
3965 const char* object_id = js->LookupParam(key: "objectId");
3966 Object& obj = Object::Handle(ptr: LookupHeapObject(thread, id_original: object_id, result: nullptr));
3967 if (obj.ptr() == Object::sentinel().ptr() || !obj.IsInstance()) {
3968 PrintInvalidParamError(js, param: "objectId");
3969 return;
3970 }
3971 const Instance& closure = Instance::Cast(obj);
3972 Breakpoint* bpt = thread->isolate()->debugger()->SetBreakpointAtActivation(
3973 closure, /*single_shot=*/false);
3974 if (bpt == nullptr) {
3975 js->PrintError(code: kCannotAddBreakpoint,
3976 details_format: "%s: Cannot add breakpoint at activation", js->method());
3977 return;
3978 }
3979 bpt->PrintJSON(stream: js);
3980}
3981
3982static const MethodParameter* const remove_breakpoint_params[] = {
3983 RUNNABLE_ISOLATE_PARAMETER,
3984 nullptr,
3985};
3986
3987static void RemoveBreakpoint(Thread* thread, JSONStream* js) {
3988 if (CheckDebuggerDisabled(thread, js)) {
3989 return;
3990 }
3991
3992 if (!js->HasParam(key: "breakpointId")) {
3993 PrintMissingParamError(js, param: "breakpointId");
3994 return;
3995 }
3996 const char* bpt_id = js->LookupParam(key: "breakpointId");
3997 ObjectIdRing::LookupResult lookup_result;
3998 Isolate* isolate = thread->isolate();
3999 Breakpoint* bpt = LookupBreakpoint(isolate, id: bpt_id, result: &lookup_result);
4000 // TODO(turnidge): Should we return a different error for bpts which
4001 // have been already removed?
4002 if (bpt == nullptr) {
4003 PrintInvalidParamError(js, param: "breakpointId");
4004 return;
4005 }
4006 isolate->debugger()->RemoveBreakpoint(bp_id: bpt->id());
4007 PrintSuccess(js);
4008}
4009
4010static void HandleNativeMetricsList(Thread* thread, JSONStream* js) {
4011 JSONObject obj(js);
4012 obj.AddProperty(name: "type", s: "MetricList");
4013 {
4014 JSONArray metrics(&obj, "metrics");
4015
4016 auto isolate = thread->isolate();
4017#define ADD_METRIC(type, variable, name, unit) \
4018 metrics.AddValue(isolate->Get##variable##Metric());
4019 ISOLATE_METRIC_LIST(ADD_METRIC);
4020#undef ADD_METRIC
4021
4022 auto isolate_group = thread->isolate_group();
4023#define ADD_METRIC(type, variable, name, unit) \
4024 metrics.AddValue(isolate_group->Get##variable##Metric());
4025 ISOLATE_GROUP_METRIC_LIST(ADD_METRIC);
4026#undef ADD_METRIC
4027 }
4028}
4029
4030static void HandleNativeMetric(Thread* thread, JSONStream* js, const char* id) {
4031 auto isolate = thread->isolate();
4032#define ADD_METRIC(type, variable, name, unit) \
4033 if (strcmp(id, name) == 0) { \
4034 isolate->Get##variable##Metric()->PrintJSON(js); \
4035 return; \
4036 }
4037 ISOLATE_METRIC_LIST(ADD_METRIC);
4038#undef ADD_METRIC
4039
4040 auto isolate_group = thread->isolate_group();
4041#define ADD_METRIC(type, variable, name, unit) \
4042 if (strcmp(id, name) == 0) { \
4043 isolate_group->Get##variable##Metric()->PrintJSON(js); \
4044 return; \
4045 }
4046 ISOLATE_GROUP_METRIC_LIST(ADD_METRIC);
4047#undef ADD_METRIC
4048
4049 PrintInvalidParamError(js, param: "metricId");
4050}
4051
4052static const MethodParameter* const get_isolate_metric_list_params[] = {
4053 RUNNABLE_ISOLATE_PARAMETER,
4054 nullptr,
4055};
4056
4057static void GetIsolateMetricList(Thread* thread, JSONStream* js) {
4058 if (js->HasParam(key: "type")) {
4059 if (!js->ParamIs(key: "type", value: "Native")) {
4060 PrintInvalidParamError(js, param: "type");
4061 return;
4062 }
4063 } else {
4064 PrintMissingParamError(js, param: "type");
4065 return;
4066 }
4067 HandleNativeMetricsList(thread, js);
4068}
4069
4070static const MethodParameter* const get_isolate_metric_params[] = {
4071 RUNNABLE_ISOLATE_PARAMETER,
4072 nullptr,
4073};
4074
4075static void GetIsolateMetric(Thread* thread, JSONStream* js) {
4076 const char* metric_id = js->LookupParam(key: "metricId");
4077 if (metric_id == nullptr) {
4078 PrintMissingParamError(js, param: "metricId");
4079 return;
4080 }
4081 // Verify id begins with "metrics/native/".
4082 static const char* const kNativeMetricIdPrefix = "metrics/native/";
4083 static intptr_t kNativeMetricIdPrefixLen = strlen(kNativeMetricIdPrefix);
4084 if (strncmp(metric_id, kNativeMetricIdPrefix, kNativeMetricIdPrefixLen) !=
4085 0) {
4086 PrintInvalidParamError(js, param: "metricId");
4087 return;
4088 }
4089 const char* id = metric_id + kNativeMetricIdPrefixLen;
4090 HandleNativeMetric(thread, js, id);
4091}
4092
4093enum TimelineOrSamplesResponseFormat : bool { JSON = false, Perfetto = true };
4094
4095static void GetCpuSamplesCommon(TimelineOrSamplesResponseFormat format,
4096 Thread* thread,
4097 JSONStream* js) {
4098 const int64_t time_origin_micros =
4099 Int64Parameter::Parse(value: js->LookupParam(key: "timeOriginMicros"));
4100 const int64_t time_extent_micros =
4101 Int64Parameter::Parse(value: js->LookupParam(key: "timeExtentMicros"));
4102 const bool include_code_samples =
4103 BoolParameter::Parse(value: js->LookupParam(key: "_code"), default_value: false);
4104 if (CheckProfilerDisabled(thread, js)) {
4105 return;
4106 }
4107
4108 if (format == TimelineOrSamplesResponseFormat::JSON) {
4109 ProfilerService::PrintJSON(stream: js, time_origin_micros, time_extent_micros,
4110 include_code_samples);
4111 } else if (format == TimelineOrSamplesResponseFormat::Perfetto) {
4112#if defined(SUPPORT_PERFETTO)
4113 // This branch will never be reached when SUPPORT_PERFETTO is not defined,
4114 // because |GetPerfettoCpuSamples| is not defined when SUPPORT_PERFETTO is
4115 // not defined.
4116 ProfilerService::PrintPerfetto(js, time_origin_micros, time_extent_micros);
4117#else
4118 UNREACHABLE();
4119#endif // defined(SUPPORT_PERFETTO)
4120 }
4121}
4122
4123inline void GetVMTimelineCommon(TimelineOrSamplesResponseFormat format,
4124 Thread* thread,
4125 JSONStream* js) {
4126 Isolate* isolate = thread->isolate();
4127 ASSERT(isolate != nullptr);
4128 StackZone zone(thread);
4129 TimelineEventRecorder* timeline_recorder = Timeline::recorder();
4130 ASSERT(timeline_recorder != nullptr);
4131 const char* name = timeline_recorder->name();
4132 if (strcmp(name, CALLBACK_RECORDER_NAME) == 0) {
4133 js->PrintError(code: kInvalidTimelineRequest,
4134 details_format: "A recorder of type \"%s\" is currently in use. As a "
4135 "result, timeline events are handled by the embedder rather "
4136 "than the VM.",
4137 timeline_recorder->name());
4138 return;
4139 } else if (strcmp(name, FUCHSIA_RECORDER_NAME) == 0 ||
4140 strcmp(name, SYSTRACE_RECORDER_NAME) == 0 ||
4141 strcmp(name, MACOS_RECORDER_NAME) == 0) {
4142 js->PrintError(
4143 code: kInvalidTimelineRequest,
4144 details_format: "A recorder of type \"%s\" is currently in use. As a result, timeline "
4145 "events are handled by the OS rather than the VM. See the VM service "
4146 "documentation for more details on where timeline events can be found "
4147 "for this recorder type.",
4148 timeline_recorder->name());
4149 return;
4150 } else if (strcmp(name, FILE_RECORDER_NAME) == 0 ||
4151 strcmp(name, PERFETTO_FILE_RECORDER_NAME) == 0) {
4152 js->PrintError(code: kInvalidTimelineRequest,
4153 details_format: "A recorder of type \"%s\" is currently in use. As a "
4154 "result, timeline events are written directly to a file and "
4155 "thus cannot be retrieved through the VM Service.",
4156 timeline_recorder->name());
4157 return;
4158 }
4159 int64_t time_origin_micros =
4160 Int64Parameter::Parse(value: js->LookupParam(key: "timeOriginMicros"));
4161 int64_t time_extent_micros =
4162 Int64Parameter::Parse(value: js->LookupParam(key: "timeExtentMicros"));
4163 TimelineEventFilter filter(time_origin_micros, time_extent_micros);
4164 if (format == TimelineOrSamplesResponseFormat::JSON) {
4165 timeline_recorder->PrintJSON(js, filter: &filter);
4166 } else if (format == TimelineOrSamplesResponseFormat::Perfetto) {
4167#if defined(SUPPORT_PERFETTO)
4168 // This branch will never be reached when SUPPORT_PERFETTO is not defined,
4169 // because |GetPerfettoVMTimeline| is not defined when SUPPORT_PERFETTO is
4170 // not defined.
4171 timeline_recorder->PrintPerfettoTimeline(js, filter);
4172#else
4173 UNREACHABLE();
4174#endif // defined(SUPPORT_PERFETTO)
4175 }
4176}
4177
4178#if defined(SUPPORT_PERFETTO)
4179static void GetPerfettoCpuSamples(Thread* thread, JSONStream* js) {
4180 GetCpuSamplesCommon(format: TimelineOrSamplesResponseFormat::Perfetto, thread, js);
4181}
4182
4183static void GetPerfettoVMTimeline(Thread* thread, JSONStream* js) {
4184 GetVMTimelineCommon(format: TimelineOrSamplesResponseFormat::Perfetto, thread, js);
4185}
4186#endif // defined(SUPPORT_PERFETTO)
4187
4188static void SetVMTimelineFlags(Thread* thread, JSONStream* js) {
4189#if !defined(SUPPORT_TIMELINE)
4190 PrintSuccess(js);
4191#else
4192 Isolate* isolate = thread->isolate();
4193 ASSERT(isolate != nullptr);
4194 StackZone zone(thread);
4195
4196 char* recorded_streams = Utils::StrDup(s: js->LookupParam(key: "recordedStreams"));
4197 Service::EnableTimelineStreams(categories_list: recorded_streams);
4198 free(recorded_streams);
4199
4200 PrintSuccess(js);
4201#endif
4202}
4203
4204static const MethodParameter* const get_vm_timeline_flags_params[] = {
4205 NO_ISOLATE_PARAMETER,
4206 nullptr,
4207};
4208
4209static void GetVMTimelineFlags(Thread* thread, JSONStream* js) {
4210#if !defined(SUPPORT_TIMELINE)
4211 JSONObject obj(js);
4212 obj.AddProperty("type", "TimelineFlags");
4213#else
4214 Isolate* isolate = thread->isolate();
4215 ASSERT(isolate != nullptr);
4216 StackZone zone(thread);
4217 Timeline::PrintFlagsToJSON(json: js);
4218#endif
4219}
4220
4221static const MethodParameter* const get_vm_timeline_micros_params[] = {
4222 NO_ISOLATE_PARAMETER,
4223 nullptr,
4224};
4225
4226static void GetVMTimelineMicros(Thread* thread, JSONStream* js) {
4227 JSONObject obj(js);
4228 obj.AddProperty(name: "type", s: "Timestamp");
4229 obj.AddPropertyTimeMicros(name: "timestamp", micros: OS::GetCurrentMonotonicMicros());
4230}
4231
4232static const MethodParameter* const clear_vm_timeline_params[] = {
4233 NO_ISOLATE_PARAMETER,
4234 nullptr,
4235};
4236
4237static void ClearVMTimeline(Thread* thread, JSONStream* js) {
4238 Isolate* isolate = thread->isolate();
4239 ASSERT(isolate != nullptr);
4240 StackZone zone(thread);
4241
4242 Timeline::Clear();
4243
4244 PrintSuccess(js);
4245}
4246
4247static const MethodParameter* const get_vm_timeline_params[] = {
4248 NO_ISOLATE_PARAMETER,
4249 new Int64Parameter("timeOriginMicros", /*required=*/false),
4250 new Int64Parameter("timeExtentMicros", /*required=*/false),
4251 nullptr,
4252};
4253
4254static void GetVMTimeline(Thread* thread, JSONStream* js) {
4255 GetVMTimelineCommon(format: TimelineOrSamplesResponseFormat::JSON, thread, js);
4256}
4257
4258static const char* const step_enum_names[] = {
4259 "None", "Into", "Over", "Out", "Rewind", "OverAsyncSuspension", nullptr,
4260};
4261
4262static const Debugger::ResumeAction step_enum_values[] = {
4263 Debugger::kContinue, Debugger::kStepInto,
4264 Debugger::kStepOver, Debugger::kStepOut,
4265 Debugger::kStepRewind, Debugger::kStepOverAsyncSuspension,
4266 Debugger::kContinue, // Default value
4267};
4268
4269static const MethodParameter* const resume_params[] = {
4270 RUNNABLE_ISOLATE_PARAMETER,
4271 new EnumParameter("step", false, step_enum_names),
4272 new UIntParameter("frameIndex", false),
4273 nullptr,
4274};
4275
4276static void Resume(Thread* thread, JSONStream* js) {
4277 const char* step_param = js->LookupParam(key: "step");
4278 Debugger::ResumeAction step = Debugger::kContinue;
4279 if (step_param != nullptr) {
4280 step = EnumMapper(value: step_param, enums: step_enum_names, values: step_enum_values);
4281 }
4282 intptr_t frame_index = 1;
4283 const char* frame_index_param = js->LookupParam(key: "frameIndex");
4284 if (frame_index_param != nullptr) {
4285 if (step != Debugger::kStepRewind) {
4286 // Only rewind supports the frameIndex parameter.
4287 js->PrintError(
4288 code: kInvalidParams,
4289 details_format: "%s: the 'frameIndex' parameter can only be used when rewinding",
4290 js->method());
4291 return;
4292 }
4293 frame_index = UIntParameter::Parse(value: js->LookupParam(key: "frameIndex"));
4294 }
4295 Isolate* isolate = thread->isolate();
4296 if (isolate->message_handler()->is_paused_on_start()) {
4297 // If the user is issuing a 'Over' or an 'Out' step, that is the
4298 // same as a regular resume request.
4299 if (step == Debugger::kStepInto) {
4300 isolate->debugger()->EnterSingleStepMode();
4301 }
4302 isolate->message_handler()->set_should_pause_on_start(false);
4303 isolate->SetResumeRequest();
4304 if (Service::debug_stream.enabled()) {
4305 ServiceEvent event(isolate, ServiceEvent::kResume);
4306 Service::HandleEvent(event: &event);
4307 }
4308 PrintSuccess(js);
4309 return;
4310 }
4311 if (isolate->message_handler()->should_pause_on_start()) {
4312 isolate->message_handler()->set_should_pause_on_start(false);
4313 isolate->SetResumeRequest();
4314 if (Service::debug_stream.enabled()) {
4315 ServiceEvent event(isolate, ServiceEvent::kResume);
4316 Service::HandleEvent(event: &event);
4317 }
4318 PrintSuccess(js);
4319 return;
4320 }
4321 if (isolate->message_handler()->is_paused_on_exit()) {
4322 isolate->message_handler()->set_should_pause_on_exit(false);
4323 isolate->SetResumeRequest();
4324 // We don't send a resume event because we will be exiting.
4325 PrintSuccess(js);
4326 return;
4327 }
4328 if (isolate->debugger()->PauseEvent() == nullptr) {
4329 js->PrintError(code: kIsolateMustBePaused, details_format: nullptr);
4330 return;
4331 }
4332
4333 const char* error = nullptr;
4334 if (!isolate->debugger()->SetResumeAction(action: step, frame_index, error: &error)) {
4335 js->PrintError(code: kCannotResume, details_format: "%s", error);
4336 return;
4337 }
4338 isolate->SetResumeRequest();
4339 PrintSuccess(js);
4340}
4341
4342static const MethodParameter* const kill_params[] = {
4343 RUNNABLE_ISOLATE_PARAMETER,
4344 nullptr,
4345};
4346
4347static void Kill(Thread* thread, JSONStream* js) {
4348 const String& msg =
4349 String::Handle(ptr: String::New(cstr: "isolate terminated by Kill service request"));
4350 const UnwindError& error = UnwindError::Handle(ptr: UnwindError::New(message: msg));
4351 error.set_is_user_initiated(true);
4352 Thread::Current()->set_sticky_error(error);
4353 PrintSuccess(js);
4354}
4355
4356static const MethodParameter* const pause_params[] = {
4357 RUNNABLE_ISOLATE_PARAMETER,
4358 nullptr,
4359};
4360
4361static void Pause(Thread* thread, JSONStream* js) {
4362 // TODO(turnidge): This interrupt message could have been sent from
4363 // the service isolate directly, but would require some special case
4364 // code. That would prevent this isolate getting double-interrupted
4365 // with OOB messages.
4366 Isolate* isolate = thread->isolate();
4367 isolate->SendInternalLibMessage(msg_id: Isolate::kInterruptMsg,
4368 capability: isolate->pause_capability());
4369 PrintSuccess(js);
4370}
4371
4372static const MethodParameter* const enable_profiler_params[] = {
4373 nullptr,
4374};
4375
4376static void EnableProfiler(Thread* thread, JSONStream* js) {
4377 if (!FLAG_profiler) {
4378 FLAG_profiler = true;
4379 Profiler::Init();
4380 }
4381 PrintSuccess(js);
4382}
4383
4384static const MethodParameter* const get_tag_profile_params[] = {
4385 RUNNABLE_ISOLATE_PARAMETER,
4386 nullptr,
4387};
4388
4389static void GetTagProfile(Thread* thread, JSONStream* js) {
4390 JSONObject miniProfile(js);
4391 miniProfile.AddProperty(name: "type", s: "TagProfile");
4392 thread->isolate()->vm_tag_counters()->PrintToJSONObject(obj: &miniProfile);
4393}
4394
4395static const MethodParameter* const get_cpu_samples_params[] = {
4396 RUNNABLE_ISOLATE_PARAMETER,
4397 new Int64Parameter("timeOriginMicros", /*required=*/false),
4398 new Int64Parameter("timeExtentMicros", /*required=*/false),
4399 nullptr,
4400};
4401
4402static void GetCpuSamples(Thread* thread, JSONStream* js) {
4403 GetCpuSamplesCommon(format: TimelineOrSamplesResponseFormat::JSON, thread, js);
4404}
4405
4406static const MethodParameter* const get_allocation_traces_params[] = {
4407 RUNNABLE_ISOLATE_PARAMETER,
4408 new IdParameter("classId", false),
4409 new Int64Parameter("timeOriginMicros", false),
4410 new Int64Parameter("timeExtentMicros", false),
4411 nullptr,
4412};
4413
4414static void GetAllocationTraces(Thread* thread, JSONStream* js) {
4415 int64_t time_origin_micros =
4416 Int64Parameter::Parse(value: js->LookupParam(key: "timeOriginMicros"));
4417 int64_t time_extent_micros =
4418 Int64Parameter::Parse(value: js->LookupParam(key: "timeExtentMicros"));
4419 Isolate* isolate = thread->isolate();
4420
4421 // Return only allocations for objects with classId.
4422 if (js->HasParam(key: "classId")) {
4423 const char* class_id = js->LookupParam(key: "classId");
4424 intptr_t cid = -1;
4425 GetPrefixedIntegerId(s: class_id, prefix: "classes/", service_id: &cid);
4426 if (IsValidClassId(isolate, cid)) {
4427 if (CheckProfilerDisabled(thread, js)) {
4428 return;
4429 }
4430 const Class& cls = Class::Handle(ptr: GetClassForId(isolate, cid));
4431 ProfilerService::PrintAllocationJSON(stream: js, cls, time_origin_micros,
4432 time_extent_micros);
4433 } else {
4434 PrintInvalidParamError(js, param: "classId");
4435 }
4436 } else {
4437 // Otherwise, return allocations for all traced class IDs.
4438 if (CheckProfilerDisabled(thread, js)) {
4439 return;
4440 }
4441 ProfilerService::PrintAllocationJSON(stream: js, time_origin_micros,
4442 time_extent_micros);
4443 }
4444}
4445
4446static const MethodParameter* const clear_cpu_samples_params[] = {
4447 RUNNABLE_ISOLATE_PARAMETER,
4448 nullptr,
4449};
4450
4451static void ClearCpuSamples(Thread* thread, JSONStream* js) {
4452 ProfilerService::ClearSamples();
4453 PrintSuccess(js);
4454}
4455
4456static void GetAllocationProfileImpl(Thread* thread,
4457 JSONStream* js,
4458 bool internal) {
4459 bool should_reset_accumulator = false;
4460 bool should_collect = false;
4461 if (js->HasParam(key: "reset")) {
4462 if (js->ParamIs(key: "reset", value: "true")) {
4463 should_reset_accumulator = true;
4464 } else {
4465 PrintInvalidParamError(js, param: "reset");
4466 return;
4467 }
4468 }
4469 if (js->HasParam(key: "gc")) {
4470 if (js->ParamIs(key: "gc", value: "true")) {
4471 should_collect = true;
4472 } else {
4473 PrintInvalidParamError(js, param: "gc");
4474 return;
4475 }
4476 }
4477 auto isolate_group = thread->isolate_group();
4478 if (should_reset_accumulator) {
4479 isolate_group->UpdateLastAllocationProfileAccumulatorResetTimestamp();
4480 }
4481 if (should_collect) {
4482 isolate_group->UpdateLastAllocationProfileGCTimestamp();
4483 isolate_group->heap()->CollectAllGarbage(reason: GCReason::kDebugging);
4484 }
4485 isolate_group->class_table()->AllocationProfilePrintJSON(stream: js, internal);
4486}
4487
4488static const MethodParameter* const get_allocation_profile_params[] = {
4489 RUNNABLE_ISOLATE_PARAMETER,
4490 nullptr,
4491};
4492
4493static void GetAllocationProfilePublic(Thread* thread, JSONStream* js) {
4494 GetAllocationProfileImpl(thread, js, internal: false);
4495}
4496
4497static void GetAllocationProfile(Thread* thread, JSONStream* js) {
4498 GetAllocationProfileImpl(thread, js, internal: true);
4499}
4500
4501static const MethodParameter* const collect_all_garbage_params[] = {
4502 RUNNABLE_ISOLATE_PARAMETER,
4503 nullptr,
4504};
4505
4506static void CollectAllGarbage(Thread* thread, JSONStream* js) {
4507 auto heap = thread->isolate_group()->heap();
4508 heap->CollectAllGarbage(reason: GCReason::kDebugging);
4509 PrintSuccess(js);
4510}
4511
4512static const MethodParameter* const get_heap_map_params[] = {
4513 RUNNABLE_ISOLATE_PARAMETER,
4514 nullptr,
4515};
4516
4517static void GetHeapMap(Thread* thread, JSONStream* js) {
4518 auto isolate_group = thread->isolate_group();
4519 if (js->HasParam(key: "gc")) {
4520 if (js->ParamIs(key: "gc", value: "scavenge")) {
4521 isolate_group->heap()->CollectGarbage(thread, type: GCType::kScavenge,
4522 reason: GCReason::kDebugging);
4523 } else if (js->ParamIs(key: "gc", value: "mark-sweep")) {
4524 isolate_group->heap()->CollectGarbage(thread, type: GCType::kMarkSweep,
4525 reason: GCReason::kDebugging);
4526 } else if (js->ParamIs(key: "gc", value: "mark-compact")) {
4527 isolate_group->heap()->CollectGarbage(thread, type: GCType::kMarkCompact,
4528 reason: GCReason::kDebugging);
4529 } else {
4530 PrintInvalidParamError(js, param: "gc");
4531 return;
4532 }
4533 }
4534 isolate_group->heap()->PrintHeapMapToJSONStream(isolate_group, stream: js);
4535}
4536
4537static const MethodParameter* const request_heap_snapshot_params[] = {
4538 RUNNABLE_ISOLATE_PARAMETER,
4539 nullptr,
4540};
4541
4542static void RequestHeapSnapshot(Thread* thread, JSONStream* js) {
4543 if (Service::heapsnapshot_stream.enabled()) {
4544 VmServiceHeapSnapshotChunkedWriter vmservice_writer(thread);
4545 HeapSnapshotWriter writer(thread, &vmservice_writer);
4546 writer.Write();
4547 }
4548 // TODO(koda): Provide some id that ties this request to async response(s).
4549 PrintSuccess(js);
4550}
4551
4552#if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_ANDROID)
4553struct VMMapping {
4554 char path[256];
4555 size_t size;
4556};
4557
4558static void AddVMMappings(JSONArray* rss_children) {
4559 FILE* fp = fopen("/proc/self/smaps", "r");
4560 if (fp == nullptr) {
4561 return;
4562 }
4563
4564 MallocGrowableArray<VMMapping> mappings(10);
4565 char line[256];
4566 char path[256];
4567 char property[32];
4568 size_t start, end, size;
4569 while (fgets(line, sizeof(line), fp) != nullptr) {
4570 if (sscanf(line, "%zx-%zx", &start, &end) == 2) {
4571 // Mapping line.
4572 strncpy(path, strrchr(line, ' ') + 1, sizeof(path) - 1);
4573 int len = strlen(path);
4574 if ((len > 0) && path[len - 1] == '\n') {
4575 path[len - 1] = 0;
4576 }
4577 } else if (sscanf(line, "%s%zd", property, &size) == 2) {
4578 // Property line.
4579 // Skipping a few paths to avoid double counting:
4580 // (deleted) - memfd dual mapping in Dart heap
4581 // [heap] - sbrk area, should already included with malloc
4582 // <empty> - anonymous mappings, mostly in Dart heap (Linux)
4583 // [anon:dart-*] - as labelled (Android)
4584 if ((strcmp(property, "Rss:") == 0) && (size != 0) &&
4585 (strcmp(path, "(deleted)") != 0) && (strcmp(path, "[heap]") != 0) &&
4586 (strcmp(path, "") != 0) && (strcmp(path, "[anon:dart-heap]") != 0) &&
4587 (strcmp(path, "[anon:dart-code]") != 0) &&
4588 (strcmp(path, "[anon:dart-profiler]") != 0) &&
4589 (strcmp(path, "[anon:dart-timeline]") != 0) &&
4590 (strcmp(path, "[anon:dart-zone]") != 0)) {
4591 bool updated = false;
4592 for (intptr_t i = 0; i < mappings.length(); i++) {
4593 if (strcmp(mappings[i].path, path) == 0) {
4594 mappings[i].size += size;
4595 updated = true;
4596 break;
4597 }
4598 }
4599 if (!updated) {
4600 VMMapping mapping;
4601 strncpy(mapping.path, path, sizeof(mapping.path));
4602 mapping.size = size;
4603 mappings.Add(value: mapping);
4604 }
4605 }
4606 }
4607 }
4608 fclose(fp);
4609
4610 for (intptr_t i = 0; i < mappings.length(); i++) {
4611 JSONObject mapping(rss_children);
4612 mapping.AddProperty(name: "name", s: mappings[i].path);
4613 mapping.AddProperty(name: "description",
4614 s: "Mapped file / shared library / executable");
4615 mapping.AddProperty64(name: "size", i: mappings[i].size * KB);
4616 JSONArray(&mapping, "children");
4617 }
4618}
4619#endif
4620
4621static intptr_t GetProcessMemoryUsageHelper(JSONStream* js) {
4622 JSONObject response(js);
4623 response.AddProperty(name: "type", s: "ProcessMemoryUsage");
4624
4625 JSONObject rss(&response, "root");
4626 rss.AddPropertyF("name", "Process %" Pd "", OS::ProcessId());
4627 rss.AddProperty(name: "description", s: "Resident set size");
4628 rss.AddProperty64(name: "size", i: Service::CurrentRSS());
4629 JSONArray rss_children(&rss, "children");
4630
4631 intptr_t vm_size = 0;
4632 {
4633 JSONObject vm(&rss_children);
4634 {
4635 JSONArray vm_children(&vm, "children");
4636
4637 {
4638 JSONObject profiler(&vm_children);
4639 profiler.AddProperty(name: "name", s: "Profiler");
4640 profiler.AddProperty(name: "description",
4641 s: "Samples from the Dart VM's profiler");
4642 intptr_t size = Profiler::Size();
4643 vm_size += size;
4644 profiler.AddProperty64(name: "size", i: size);
4645 JSONArray(&profiler, "children");
4646 }
4647
4648 {
4649 JSONObject timeline(&vm_children);
4650 timeline.AddProperty(name: "name", s: "Timeline");
4651 timeline.AddProperty(
4652 name: "description",
4653 s: "Timeline events from dart:developer and Dart_TimelineEvent");
4654 intptr_t size = Timeline::recorder()->Size();
4655 vm_size += size;
4656 timeline.AddProperty64(name: "size", i: size);
4657 JSONArray(&timeline, "children");
4658 }
4659
4660 {
4661 JSONObject zone(&vm_children);
4662 zone.AddProperty(name: "name", s: "Zone");
4663 zone.AddProperty(name: "description", s: "Arena allocation in the Dart VM");
4664 intptr_t size = Zone::Size();
4665 vm_size += size;
4666 zone.AddProperty64(name: "size", i: size);
4667 JSONArray(&zone, "children");
4668 }
4669
4670 {
4671 JSONObject semi(&vm_children);
4672 semi.AddProperty(name: "name", s: "Page Cache");
4673 semi.AddProperty(name: "description", s: "Cached heap regions");
4674 intptr_t size = Page::CachedSize();
4675 vm_size += size;
4676 semi.AddProperty64(name: "size", i: size);
4677 JSONArray(&semi, "children");
4678 }
4679
4680 IsolateGroup::ForEach([&vm_children,
4681 &vm_size](IsolateGroup* isolate_group) {
4682 // Note: new_space()->CapacityInWords() includes memory that hasn't been
4683 // allocated from the OS yet.
4684 int64_t capacity =
4685 (isolate_group->heap()->new_space()->UsedInWords() +
4686 isolate_group->heap()->old_space()->CapacityInWords()) *
4687 kWordSize;
4688 int64_t used = isolate_group->heap()->TotalUsedInWords() * kWordSize;
4689 int64_t free = capacity - used;
4690
4691 JSONObject group(&vm_children);
4692 group.AddPropertyF(name: "name", format: "IsolateGroup %s",
4693 isolate_group->source()->name);
4694 group.AddProperty(name: "description", s: "Dart heap capacity");
4695 vm_size += capacity;
4696 group.AddProperty64(name: "size", i: capacity);
4697 JSONArray group_children(&group, "children");
4698
4699 {
4700 JSONObject jsused(&group_children);
4701 jsused.AddProperty(name: "name", s: "Used");
4702 jsused.AddProperty(name: "description", s: "");
4703 jsused.AddProperty64(name: "size", i: used);
4704 JSONArray(&jsused, "children");
4705 }
4706
4707 {
4708 JSONObject jsfree(&group_children);
4709 jsfree.AddProperty(name: "name", s: "Free");
4710 jsfree.AddProperty(name: "description", s: "");
4711 jsfree.AddProperty64(name: "size", i: free);
4712 JSONArray(&jsfree, "children");
4713 }
4714 });
4715 } // vm_children
4716
4717 vm.AddProperty(name: "name", s: "Dart VM");
4718 vm.AddProperty(name: "description", s: "");
4719 vm.AddProperty64(name: "size", i: vm_size);
4720 }
4721
4722#if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_ANDROID)
4723 AddVMMappings(rss_children: &rss_children);
4724#endif
4725 // TODO(46166): Implement for other operating systems.
4726
4727 return vm_size;
4728}
4729
4730static const MethodParameter* const get_process_memory_usage_params[] = {
4731 nullptr,
4732};
4733
4734static void GetProcessMemoryUsage(Thread* thread, JSONStream* js) {
4735 GetProcessMemoryUsageHelper(js);
4736}
4737
4738void Service::SendInspectEvent(Isolate* isolate, const Object& inspectee) {
4739 if (!Service::debug_stream.enabled()) {
4740 return;
4741 }
4742 ServiceEvent event(isolate, ServiceEvent::kInspect);
4743 event.set_inspectee(&inspectee);
4744 Service::HandleEvent(event: &event);
4745}
4746
4747void Service::SendEmbedderEvent(Isolate* isolate,
4748 const char* stream_id,
4749 const char* event_kind,
4750 const uint8_t* bytes,
4751 intptr_t bytes_len) {
4752 ServiceEvent event(isolate, ServiceEvent::kEmbedder);
4753 event.set_embedder_kind(event_kind);
4754 event.set_embedder_stream_id(stream_id);
4755 event.set_bytes(bytes, bytes_length: bytes_len);
4756 Service::HandleEvent(event: &event, /*enter_safepoint=*/false);
4757}
4758
4759void Service::SendLogEvent(Isolate* isolate,
4760 int64_t sequence_number,
4761 int64_t timestamp,
4762 intptr_t level,
4763 const String& name,
4764 const String& message,
4765 const Instance& zone,
4766 const Object& error,
4767 const Instance& stack_trace) {
4768 if (!Service::logging_stream.enabled()) {
4769 return;
4770 }
4771 ServiceEvent::LogRecord log_record;
4772 log_record.sequence_number = sequence_number;
4773 log_record.timestamp = timestamp;
4774 log_record.level = level;
4775 log_record.name = &name;
4776 log_record.message = &message;
4777 log_record.zone = &zone;
4778 log_record.error = &error;
4779 log_record.stack_trace = &stack_trace;
4780 ServiceEvent event(isolate, ServiceEvent::kLogging);
4781 event.set_log_record(log_record);
4782 Service::HandleEvent(event: &event);
4783}
4784
4785void Service::SendExtensionEvent(Isolate* isolate,
4786 const String& event_kind,
4787 const String& event_data) {
4788 if (!Service::extension_stream.enabled()) {
4789 return;
4790 }
4791 ServiceEvent::ExtensionEvent extension_event;
4792 extension_event.event_kind = &event_kind;
4793 extension_event.event_data = &event_data;
4794 ServiceEvent event(isolate, ServiceEvent::kExtension);
4795 event.set_extension_event(extension_event);
4796 Service::HandleEvent(event: &event);
4797}
4798
4799static const MethodParameter* const get_persistent_handles_params[] = {
4800 ISOLATE_PARAMETER,
4801 nullptr,
4802};
4803
4804template <typename T>
4805class PersistentHandleVisitor : public HandleVisitor {
4806 public:
4807 PersistentHandleVisitor(Thread* thread, JSONArray* handles)
4808 : HandleVisitor(thread), handles_(handles) {
4809 ASSERT(handles_ != nullptr);
4810 }
4811
4812 void Append(PersistentHandle* persistent_handle) {
4813 JSONObject obj(handles_);
4814 obj.AddProperty(name: "type", s: "_PersistentHandle");
4815 const Object& object = Object::Handle(ptr: persistent_handle->ptr());
4816 obj.AddProperty(name: "object", obj: object);
4817 }
4818
4819 void Append(FinalizablePersistentHandle* weak_persistent_handle) {
4820 if (!weak_persistent_handle->ptr()->IsHeapObject()) {
4821 return; // Free handle.
4822 }
4823
4824 JSONObject obj(handles_);
4825 obj.AddProperty(name: "type", s: "_WeakPersistentHandle");
4826 const Object& object = Object::Handle(ptr: weak_persistent_handle->ptr());
4827 obj.AddProperty(name: "object", obj: object);
4828 obj.AddPropertyF(
4829 "peer", "0x%" Px "",
4830 reinterpret_cast<uintptr_t>(weak_persistent_handle->peer()));
4831 obj.AddPropertyF(
4832 "callbackAddress", "0x%" Px "",
4833 reinterpret_cast<uintptr_t>(weak_persistent_handle->callback()));
4834 // Attempt to include a native symbol name.
4835 char* name = NativeSymbolResolver::LookupSymbolName(
4836 pc: reinterpret_cast<uword>(weak_persistent_handle->callback()), start: nullptr);
4837 obj.AddProperty(name: "callbackSymbolName", s: (name == nullptr) ? "" : name);
4838 if (name != nullptr) {
4839 NativeSymbolResolver::FreeSymbolName(name);
4840 }
4841 obj.AddPropertyF("externalSize", "%" Pd "",
4842 weak_persistent_handle->external_size());
4843 }
4844
4845 protected:
4846 void VisitHandle(uword addr) override {
4847 T* handle = reinterpret_cast<T*>(addr);
4848 Append(handle);
4849 }
4850
4851 JSONArray* handles_;
4852};
4853
4854static void GetPersistentHandles(Thread* thread, JSONStream* js) {
4855 Isolate* isolate = thread->isolate();
4856 ASSERT(isolate != nullptr);
4857
4858 ApiState* api_state = isolate->group()->api_state();
4859 ASSERT(api_state != nullptr);
4860
4861 {
4862 JSONObject obj(js);
4863 obj.AddProperty(name: "type", s: "_PersistentHandles");
4864 // Persistent handles.
4865 {
4866 JSONArray persistent_handles(&obj, "persistentHandles");
4867 api_state->RunWithLockedPersistentHandles(
4868 [&](PersistentHandles& handles) {
4869 PersistentHandleVisitor<PersistentHandle> visitor(
4870 thread, &persistent_handles);
4871 handles.Visit(visitor: &visitor);
4872 });
4873 }
4874 // Weak persistent handles.
4875 {
4876 JSONArray weak_persistent_handles(&obj, "weakPersistentHandles");
4877 api_state->RunWithLockedWeakPersistentHandles(
4878 [&](FinalizablePersistentHandles& handles) {
4879 PersistentHandleVisitor<FinalizablePersistentHandle> visitor(
4880 thread, &weak_persistent_handles);
4881 handles.VisitHandles(visitor: &visitor);
4882 });
4883 }
4884 }
4885}
4886
4887static const MethodParameter* const get_ports_private_params[] = {
4888 RUNNABLE_ISOLATE_PARAMETER,
4889 nullptr,
4890};
4891
4892static void GetPortsPrivate(Thread* thread, JSONStream* js) {
4893 MessageHandler* message_handler = thread->isolate()->message_handler();
4894 PortMap::PrintPortsForMessageHandler(handler: message_handler, stream: js);
4895}
4896
4897static void RespondWithMalformedJson(Thread* thread, JSONStream* js) {
4898 JSONObject jsobj(js);
4899 jsobj.AddProperty(name: "a", s: "a");
4900 JSONObject jsobj1(js);
4901 jsobj1.AddProperty(name: "a", s: "a");
4902 JSONObject jsobj2(js);
4903 jsobj2.AddProperty(name: "a", s: "a");
4904 JSONObject jsobj3(js);
4905 jsobj3.AddProperty(name: "a", s: "a");
4906}
4907
4908static void RespondWithMalformedObject(Thread* thread, JSONStream* js) {
4909 JSONObject jsobj(js);
4910 jsobj.AddProperty(name: "bart", s: "simpson");
4911}
4912
4913// Returns |true| if a heap object with the specified ID was successfully found,
4914// and |false| otherwise. If an object was found, it will be stored at address
4915// |obj|.
4916// This function should be used to handle shared logic between |GetObject| and
4917// |GetImplementationFields|.
4918static bool GetHeapObjectCommon(Thread* thread,
4919 JSONStream* js,
4920 const char* id,
4921 Object* obj,
4922 ObjectIdRing::LookupResult* lookup_result) {
4923 *obj = LookupHeapObject(thread, id_original: id, result: lookup_result);
4924 ASSERT(obj != nullptr);
4925 ASSERT(lookup_result != nullptr);
4926 if (obj->ptr() != Object::sentinel().ptr()) {
4927#if !defined(DART_PRECOMPILED_RUNTIME)
4928 // If obj is a script from dart:* and doesn't have source loaded, try and
4929 // load the source before sending the response.
4930 if (obj->IsScript()) {
4931 const Script& script = Script::Cast(obj: *obj);
4932 if (!script.HasSource() && script.IsPartOfDartColonLibrary() &&
4933 Service::HasDartLibraryKernelForSources()) {
4934 const uint8_t* kernel_buffer = Service::dart_library_kernel();
4935 const intptr_t kernel_buffer_len =
4936 Service::dart_library_kernel_length();
4937 script.LoadSourceFromKernel(kernel_buffer, kernel_buffer_len);
4938 }
4939 }
4940#endif // !defined(DART_PRECOMPILED_RUNTIME)
4941 // We found a heap object for this id.
4942 return true;
4943 }
4944
4945 return false;
4946}
4947
4948static const MethodParameter* const get_object_params[] = {
4949 RUNNABLE_ISOLATE_PARAMETER,
4950 new UIntParameter("offset", false),
4951 new UIntParameter("count", false),
4952 nullptr,
4953};
4954
4955static void GetObject(Thread* thread, JSONStream* js) {
4956 const char* id = js->LookupParam(key: "objectId");
4957 if (id == nullptr) {
4958 PrintMissingParamError(js, param: "objectId");
4959 return;
4960 }
4961 if (js->HasParam(key: "offset")) {
4962 intptr_t value = UIntParameter::Parse(value: js->LookupParam(key: "offset"));
4963 if (value < 0) {
4964 PrintInvalidParamError(js, param: "offset");
4965 return;
4966 }
4967 js->set_offset(value);
4968 }
4969 if (js->HasParam(key: "count")) {
4970 intptr_t value = UIntParameter::Parse(value: js->LookupParam(key: "count"));
4971 if (value < 0) {
4972 PrintInvalidParamError(js, param: "count");
4973 return;
4974 }
4975 js->set_count(value);
4976 }
4977
4978 // Handle heap objects.
4979 Object& obj = Object::Handle();
4980 ObjectIdRing::LookupResult lookup_result;
4981 if (GetHeapObjectCommon(thread, js, id, obj: &obj, lookup_result: &lookup_result)) {
4982 obj.PrintJSON(stream: js, ref: false);
4983 return;
4984 } else if (lookup_result == ObjectIdRing::kCollected) {
4985 PrintSentinel(js, sentinel_type: kCollectedSentinel);
4986 return;
4987 } else if (lookup_result == ObjectIdRing::kExpired) {
4988 PrintSentinel(js, sentinel_type: kExpiredSentinel);
4989 return;
4990 }
4991
4992 // Handle non-heap objects.
4993 Breakpoint* bpt = LookupBreakpoint(isolate: thread->isolate(), id, result: &lookup_result);
4994 if (bpt != nullptr) {
4995 bpt->PrintJSON(stream: js);
4996 return;
4997 } else if (lookup_result == ObjectIdRing::kCollected) {
4998 PrintSentinel(js, sentinel_type: kCollectedSentinel);
4999 return;
5000 }
5001
5002 PrintInvalidParamError(js, param: "objectId");
5003}
5004
5005static const MethodParameter* const get_implementation_fields_params[] = {
5006 RUNNABLE_ISOLATE_PARAMETER,
5007 OBJECT_PARAMETER,
5008 nullptr,
5009};
5010
5011static void GetImplementationFields(Thread* thread, JSONStream* js) {
5012 const char* id = js->LookupParam(key: "objectId");
5013
5014 // Handle heap objects.
5015 Object& obj = Object::Handle();
5016 ObjectIdRing::LookupResult lookup_result;
5017 if (GetHeapObjectCommon(thread, js, id, obj: &obj, lookup_result: &lookup_result)) {
5018 obj.PrintImplementationFields(stream: js);
5019 return;
5020 } else if (lookup_result == ObjectIdRing::kCollected) {
5021 PrintSentinel(js, sentinel_type: kCollectedSentinel);
5022 return;
5023 } else if (lookup_result == ObjectIdRing::kExpired) {
5024 PrintSentinel(js, sentinel_type: kExpiredSentinel);
5025 return;
5026 }
5027
5028 // Handle non-heap objects.
5029 Breakpoint* bpt = LookupBreakpoint(isolate: thread->isolate(), id, result: &lookup_result);
5030 if (bpt != nullptr) {
5031 const JSONObject jsobj(js);
5032 jsobj.AddProperty(name: "type", s: "ImplementationFields");
5033 JSONArray jsarr_fields(&jsobj, "fields");
5034 return;
5035 } else if (lookup_result == ObjectIdRing::kCollected) {
5036 PrintSentinel(js, sentinel_type: kCollectedSentinel);
5037 return;
5038 }
5039
5040 PrintInvalidParamError(js, param: "objectId");
5041}
5042
5043static const MethodParameter* const get_object_store_params[] = {
5044 RUNNABLE_ISOLATE_PARAMETER,
5045 nullptr,
5046};
5047
5048static void GetObjectStore(Thread* thread, JSONStream* js) {
5049 JSONObject jsobj(js);
5050 thread->isolate_group()->object_store()->PrintToJSONObject(jsobj: &jsobj);
5051}
5052
5053static const MethodParameter* const get_isolate_object_store_params[] = {
5054 RUNNABLE_ISOLATE_PARAMETER,
5055 nullptr,
5056};
5057
5058static void GetIsolateObjectStore(Thread* thread, JSONStream* js) {
5059 JSONObject jsobj(js);
5060 thread->isolate()->isolate_object_store()->PrintToJSONObject(jsobj: &jsobj);
5061}
5062
5063static const MethodParameter* const get_class_list_params[] = {
5064 RUNNABLE_ISOLATE_PARAMETER,
5065 nullptr,
5066};
5067
5068static void GetClassList(Thread* thread, JSONStream* js) {
5069 ClassTable* table = thread->isolate_group()->class_table();
5070 JSONObject jsobj(js);
5071 table->PrintToJSONObject(object: &jsobj);
5072}
5073
5074static const MethodParameter* const get_type_arguments_list_params[] = {
5075 RUNNABLE_ISOLATE_PARAMETER,
5076 nullptr,
5077};
5078
5079static void GetTypeArgumentsList(Thread* thread, JSONStream* js) {
5080 bool only_with_instantiations = false;
5081 if (js->ParamIs(key: "onlyWithInstantiations", value: "true")) {
5082 only_with_instantiations = true;
5083 }
5084 Zone* zone = thread->zone();
5085 ObjectStore* object_store = thread->isolate_group()->object_store();
5086 CanonicalTypeArgumentsSet typeargs_table(
5087 zone, object_store->canonical_type_arguments());
5088 const intptr_t table_size = typeargs_table.NumEntries();
5089 const intptr_t table_used = typeargs_table.NumOccupied();
5090 const Array& typeargs_array =
5091 Array::Handle(zone, ptr: HashTables::ToArray(table: typeargs_table, include_payload: false));
5092 ASSERT(typeargs_array.Length() == table_used);
5093 TypeArguments& typeargs = TypeArguments::Handle(zone);
5094 JSONObject jsobj(js);
5095 jsobj.AddProperty(name: "type", s: "TypeArgumentsList");
5096 jsobj.AddProperty(name: "canonicalTypeArgumentsTableSize", i: table_size);
5097 jsobj.AddProperty(name: "canonicalTypeArgumentsTableUsed", i: table_used);
5098 JSONArray members(&jsobj, "typeArguments");
5099 for (intptr_t i = 0; i < table_used; i++) {
5100 typeargs ^= typeargs_array.At(i);
5101 if (!typeargs.IsNull()) {
5102 if (!only_with_instantiations || typeargs.HasInstantiations()) {
5103 members.AddValue(obj: typeargs);
5104 }
5105 }
5106 }
5107 typeargs_table.Release();
5108}
5109
5110static const MethodParameter* const get_version_params[] = {
5111 NO_ISOLATE_PARAMETER,
5112 nullptr,
5113};
5114
5115static void GetVersion(Thread* thread, JSONStream* js) {
5116 JSONObject jsobj(js);
5117 jsobj.AddProperty(name: "type", s: "Version");
5118 jsobj.AddProperty(name: "major",
5119 i: static_cast<intptr_t>(SERVICE_PROTOCOL_MAJOR_VERSION));
5120 jsobj.AddProperty(name: "minor",
5121 i: static_cast<intptr_t>(SERVICE_PROTOCOL_MINOR_VERSION));
5122 jsobj.AddProperty(name: "_privateMajor", i: static_cast<intptr_t>(0));
5123 jsobj.AddProperty(name: "_privateMinor", i: static_cast<intptr_t>(0));
5124}
5125
5126class ServiceIsolateVisitor : public IsolateVisitor {
5127 public:
5128 explicit ServiceIsolateVisitor(JSONArray* jsarr) : jsarr_(jsarr) {}
5129 virtual ~ServiceIsolateVisitor() {}
5130
5131 void VisitIsolate(Isolate* isolate) {
5132 if (!IsSystemIsolate(isolate) && isolate->is_service_registered()) {
5133 jsarr_->AddValue(isolate);
5134 }
5135 }
5136
5137 private:
5138 JSONArray* jsarr_;
5139};
5140
5141class SystemServiceIsolateVisitor : public IsolateVisitor {
5142 public:
5143 explicit SystemServiceIsolateVisitor(JSONArray* jsarr) : jsarr_(jsarr) {}
5144 virtual ~SystemServiceIsolateVisitor() {}
5145
5146 void VisitIsolate(Isolate* isolate) {
5147 if (IsSystemIsolate(isolate) &&
5148 !Dart::VmIsolateNameEquals(name: isolate->name())) {
5149 jsarr_->AddValue(isolate);
5150 }
5151 }
5152
5153 private:
5154 JSONArray* jsarr_;
5155};
5156
5157static const MethodParameter* const get_vm_params[] = {
5158 NO_ISOLATE_PARAMETER,
5159 nullptr,
5160};
5161
5162void Service::PrintJSONForEmbedderInformation(JSONObject* jsobj) {
5163 if (embedder_information_callback_ != nullptr) {
5164 Dart_EmbedderInformation info = {
5165 0, // version
5166 nullptr, // name
5167 -1, // max_rss
5168 -1 // current_rss
5169 };
5170 embedder_information_callback_(&info);
5171 ASSERT(info.version == DART_EMBEDDER_INFORMATION_CURRENT_VERSION);
5172 if (info.name != nullptr) {
5173 jsobj->AddProperty(name: "_embedder", s: info.name);
5174 }
5175 if (info.max_rss >= 0) {
5176 jsobj->AddProperty64(name: "_maxRSS", i: info.max_rss);
5177 }
5178 if (info.current_rss >= 0) {
5179 jsobj->AddProperty64(name: "_currentRSS", i: info.current_rss);
5180 }
5181 }
5182}
5183
5184void Service::PrintJSONForVM(JSONStream* js, bool ref) {
5185 JSONObject jsobj(js);
5186 jsobj.AddProperty(name: "type", s: (ref ? "@VM" : "VM"));
5187 jsobj.AddProperty(name: "name", s: GetVMName());
5188 if (ref) {
5189 return;
5190 }
5191 jsobj.AddProperty(name: "architectureBits", i: static_cast<intptr_t>(kBitsPerWord));
5192 jsobj.AddProperty(name: "hostCPU", s: HostCPUFeatures::hardware());
5193 jsobj.AddProperty(name: "operatingSystem", s: OS::Name());
5194 jsobj.AddProperty(name: "targetCPU", s: CPU::Id());
5195 jsobj.AddProperty(name: "version", s: Version::String());
5196#if defined(DART_PRECOMPILED_RUNTIME)
5197 Snapshot::Kind kind = Snapshot::kFullAOT;
5198#else
5199 Snapshot::Kind kind = Snapshot::kFullJIT;
5200#endif
5201 char* features_string = Dart::FeaturesString(isolate_group: nullptr, is_vm_snapshot: true, kind);
5202 jsobj.AddProperty(name: "_features", s: features_string);
5203 free(features_string);
5204 jsobj.AddProperty(name: "_profilerMode", s: FLAG_profile_vm ? "VM" : "Dart");
5205 jsobj.AddProperty64(name: "pid", i: OS::ProcessId());
5206 jsobj.AddPropertyTimeMillis(
5207 name: "startTime", millis: OS::GetCurrentTimeMillis() - Dart::UptimeMillis());
5208 PrintJSONForEmbedderInformation(jsobj: &jsobj);
5209 // Construct the isolate and isolate_groups list.
5210 {
5211 JSONArray jsarr(&jsobj, "isolates");
5212 ServiceIsolateVisitor visitor(&jsarr);
5213 Isolate::VisitIsolates(visitor: &visitor);
5214 }
5215 {
5216 JSONArray jsarr(&jsobj, "systemIsolates");
5217 SystemServiceIsolateVisitor visitor(&jsarr);
5218 Isolate::VisitIsolates(visitor: &visitor);
5219 }
5220 {
5221 JSONArray jsarr_isolate_groups(&jsobj, "isolateGroups");
5222 IsolateGroup::ForEach([&jsarr_isolate_groups](IsolateGroup* isolate_group) {
5223 if (!isolate_group->is_system_isolate_group()) {
5224 jsarr_isolate_groups.AddValue(isolate_group);
5225 }
5226 });
5227 }
5228 {
5229 JSONArray jsarr_isolate_groups(&jsobj, "systemIsolateGroups");
5230 IsolateGroup::ForEach([&jsarr_isolate_groups](IsolateGroup* isolate_group) {
5231 // Don't surface the vm-isolate since it's not a "real" isolate.
5232 if (Dart::VmIsolateNameEquals(name: isolate_group->source()->name)) {
5233 return;
5234 }
5235 if (isolate_group->is_system_isolate_group()) {
5236 jsarr_isolate_groups.AddValue(isolate_group);
5237 }
5238 });
5239 }
5240 {
5241 JSONStream discard_js;
5242 intptr_t vm_memory = GetProcessMemoryUsageHelper(js: &discard_js);
5243 jsobj.AddProperty(name: "_currentMemory", i: vm_memory);
5244 }
5245}
5246
5247static void GetVM(Thread* thread, JSONStream* js) {
5248 Service::PrintJSONForVM(js, ref: false);
5249}
5250
5251class UriMappingTraits {
5252 public:
5253 static const char* Name() { return "UriMappingTraits"; }
5254 static bool ReportStats() { return false; }
5255
5256 static bool IsMatch(const Object& a, const Object& b) {
5257 const String& a_str = String::Cast(obj: a);
5258 const String& b_str = String::Cast(obj: b);
5259
5260 ASSERT(a_str.HasHash() && b_str.HasHash());
5261 return a_str.Equals(str: b_str);
5262 }
5263
5264 static uword Hash(const Object& key) { return String::Cast(obj: key).Hash(); }
5265
5266 static ObjectPtr NewKey(const String& str) { return str.ptr(); }
5267};
5268
5269typedef UnorderedHashMap<UriMappingTraits> UriMapping;
5270
5271static void PopulateUriMappings(Thread* thread) {
5272 Zone* zone = thread->zone();
5273 auto object_store = thread->isolate_group()->object_store();
5274 UriMapping uri_to_resolved_uri(HashTables::New<UriMapping>(initial_capacity: 16, space: Heap::kOld));
5275 UriMapping resolved_uri_to_uri(HashTables::New<UriMapping>(initial_capacity: 16, space: Heap::kOld));
5276
5277 const auto& libs =
5278 GrowableObjectArray::Handle(zone, ptr: object_store->libraries());
5279 intptr_t num_libs = libs.Length();
5280
5281 Library& lib = Library::Handle(zone);
5282 Script& script = Script::Handle(zone);
5283 Array& scripts = Array::Handle(zone);
5284 String& uri = String::Handle(zone);
5285 String& resolved_uri = String::Handle(zone);
5286#if defined(DART_HOST_OS_WINDOWS) || defined(DART_HOST_OS_MACOS)
5287 String& tmp = thread->StringHandle();
5288#endif
5289 for (intptr_t i = 0; i < num_libs; ++i) {
5290 lib ^= libs.At(i);
5291 scripts ^= lib.LoadedScripts();
5292 intptr_t num_scripts = scripts.Length();
5293 for (intptr_t j = 0; j < num_scripts; ++j) {
5294 script ^= scripts.At(j);
5295 uri ^= script.url();
5296 resolved_uri ^= script.resolved_url();
5297 uri_to_resolved_uri.UpdateOrInsert(key: uri, value: resolved_uri);
5298 resolved_uri_to_uri.UpdateOrInsert(key: resolved_uri, value: uri);
5299
5300#if defined(DART_HOST_OS_WINDOWS) || defined(DART_HOST_OS_MACOS)
5301 // Allow for case insensitive matching on platforms that might allow for
5302 // case insensitive paths.
5303 tmp = String::ToLowerCase(uri);
5304 uri_to_resolved_uri.UpdateOrInsert(tmp, resolved_uri);
5305 tmp = String::ToLowerCase(resolved_uri);
5306 resolved_uri_to_uri.UpdateOrInsert(tmp, uri);
5307#endif
5308 }
5309 }
5310
5311 object_store->set_uri_to_resolved_uri_map(uri_to_resolved_uri.Release());
5312 object_store->set_resolved_uri_to_uri_map(resolved_uri_to_uri.Release());
5313 Smi& count = Smi::Handle(zone, ptr: Smi::New(value: num_libs));
5314 object_store->set_last_libraries_count(count);
5315}
5316
5317static void LookupScriptUrisImpl(Thread* thread,
5318 JSONStream* js,
5319 bool lookup_resolved) {
5320 Zone* zone = thread->zone();
5321 auto object_store = thread->isolate_group()->object_store();
5322
5323 const auto& libs =
5324 GrowableObjectArray::Handle(zone, ptr: object_store->libraries());
5325 Smi& last_libraries_count =
5326 Smi::Handle(zone, ptr: object_store->last_libraries_count());
5327 if ((object_store->uri_to_resolved_uri_map() == Array::null()) ||
5328 (object_store->resolved_uri_to_uri_map() == Array::null()) ||
5329 (last_libraries_count.Value() != libs.Length())) {
5330 PopulateUriMappings(thread);
5331 }
5332 const char* uris_arg = js->LookupParam(key: "uris");
5333 if (uris_arg == nullptr) {
5334 PrintMissingParamError(js, param: "uris");
5335 return;
5336 }
5337
5338 const GrowableObjectArray& uris =
5339 GrowableObjectArray::Handle(zone, ptr: GrowableObjectArray::New());
5340 intptr_t uris_length = ParseJSONArray(thread, str: uris_arg, elements: uris);
5341 if (uris_length < 0) {
5342 PrintInvalidParamError(js, param: "uris");
5343 return;
5344 }
5345
5346 UriMapping map(lookup_resolved ? object_store->uri_to_resolved_uri_map()
5347 : object_store->resolved_uri_to_uri_map());
5348 JSONObject jsobj(js);
5349 jsobj.AddProperty(name: "type", s: "UriList");
5350
5351 {
5352 JSONArray uris_array(&jsobj, "uris");
5353 String& uri = String::Handle(zone);
5354 String& res = String::Handle(zone);
5355 for (intptr_t i = 0; i < uris.Length(); ++i) {
5356 uri ^= uris.At(index: i);
5357 res ^= map.GetOrNull(key: uri);
5358#if defined(DART_HOST_OS_WINDOWS) || defined(DART_HOST_OS_MACOS)
5359 // Windows and MacOS paths can be case insensitive, so we should allow for
5360 // case insensitive URI mappings on Windows and MacOS.
5361 if (res.IsNull()) {
5362 String& lower_case_uri = thread->StringHandle();
5363 lower_case_uri = String::ToLowerCase(uri);
5364 res ^= map.GetOrNull(lower_case_uri);
5365 }
5366#endif // defined(DART_HOST_OS_WINDOWS) || defined(DART_HOST_OS_MACOS)
5367 if (res.IsNull()) {
5368 uris_array.AddValueNull();
5369 } else {
5370 uris_array.AddValue(s: res.ToCString());
5371 }
5372 }
5373 }
5374 map.Release();
5375}
5376
5377static const MethodParameter* const lookup_resolved_package_uris_params[] = {
5378 ISOLATE_PARAMETER,
5379 nullptr,
5380};
5381
5382static void LookupResolvedPackageUris(Thread* thread, JSONStream* js) {
5383 LookupScriptUrisImpl(thread, js, lookup_resolved: true);
5384}
5385
5386static const MethodParameter* const lookup_package_uris_params[] = {
5387 ISOLATE_PARAMETER,
5388 nullptr,
5389};
5390
5391static void LookupPackageUris(Thread* thread, JSONStream* js) {
5392 LookupScriptUrisImpl(thread, js, lookup_resolved: false);
5393}
5394
5395static const char* const exception_pause_mode_names[] = {
5396 "All",
5397 "None",
5398 "Unhandled",
5399 nullptr,
5400};
5401
5402static Dart_ExceptionPauseInfo exception_pause_mode_values[] = {
5403 kPauseOnAllExceptions,
5404 kNoPauseOnExceptions,
5405 kPauseOnUnhandledExceptions,
5406 kInvalidExceptionPauseInfo,
5407};
5408
5409static const MethodParameter* const set_exception_pause_mode_params[] = {
5410 ISOLATE_PARAMETER,
5411 new EnumParameter("mode", true, exception_pause_mode_names),
5412 nullptr,
5413};
5414
5415static void SetExceptionPauseMode(Thread* thread, JSONStream* js) {
5416 const char* mode = js->LookupParam(key: "mode");
5417 if (mode == nullptr) {
5418 PrintMissingParamError(js, param: "mode");
5419 return;
5420 }
5421 Dart_ExceptionPauseInfo info =
5422 EnumMapper(value: mode, enums: exception_pause_mode_names, values: exception_pause_mode_values);
5423 if (info == kInvalidExceptionPauseInfo) {
5424 PrintInvalidParamError(js, param: "mode");
5425 return;
5426 }
5427 Isolate* isolate = thread->isolate();
5428 isolate->debugger()->SetExceptionPauseInfo(info);
5429 if (Service::debug_stream.enabled()) {
5430 ServiceEvent event(isolate, ServiceEvent::kDebuggerSettingsUpdate);
5431 Service::HandleEvent(event: &event);
5432 }
5433 PrintSuccess(js);
5434}
5435
5436static const MethodParameter* const set_isolate_pause_mode_params[] = {
5437 ISOLATE_PARAMETER,
5438 new EnumParameter("exceptionPauseMode", false, exception_pause_mode_names),
5439 new BoolParameter("shouldPauseOnExit", false),
5440 nullptr,
5441};
5442
5443static void SetIsolatePauseMode(Thread* thread, JSONStream* js) {
5444 bool state_changed = false;
5445 const char* exception_pause_mode = js->LookupParam(key: "exceptionPauseMode");
5446 if (exception_pause_mode != nullptr) {
5447 Dart_ExceptionPauseInfo info =
5448 EnumMapper(value: exception_pause_mode, enums: exception_pause_mode_names,
5449 values: exception_pause_mode_values);
5450 if (info == kInvalidExceptionPauseInfo) {
5451 PrintInvalidParamError(js, param: "exceptionPauseMode");
5452 return;
5453 }
5454 Isolate* isolate = thread->isolate();
5455 isolate->debugger()->SetExceptionPauseInfo(info);
5456 state_changed = true;
5457 }
5458
5459 const char* pause_isolate_on_exit = js->LookupParam(key: "shouldPauseOnExit");
5460 if (pause_isolate_on_exit != nullptr) {
5461 bool enable = BoolParameter::Parse(value: pause_isolate_on_exit, default_value: false);
5462 thread->isolate()->message_handler()->set_should_pause_on_exit(enable);
5463 state_changed = true;
5464 }
5465
5466 if (state_changed && Service::debug_stream.enabled()) {
5467 ServiceEvent event(thread->isolate(),
5468 ServiceEvent::kDebuggerSettingsUpdate);
5469 Service::HandleEvent(event: &event);
5470 }
5471 PrintSuccess(js);
5472}
5473
5474static const MethodParameter* const set_breakpoint_state_params[] = {
5475 ISOLATE_PARAMETER,
5476 new IdParameter("breakpointId", true),
5477 new BoolParameter("enable", true),
5478 nullptr,
5479};
5480
5481static void SetBreakpointState(Thread* thread, JSONStream* js) {
5482 Isolate* isolate = thread->isolate();
5483 const char* bpt_id = js->LookupParam(key: "breakpointId");
5484 bool enable = BoolParameter::Parse(value: js->LookupParam(key: "enable"), default_value: true);
5485 ObjectIdRing::LookupResult lookup_result;
5486 Breakpoint* bpt = LookupBreakpoint(isolate, id: bpt_id, result: &lookup_result);
5487 // TODO(bkonyi): Should we return a different error for bpts which
5488 // have been already removed?
5489 if (bpt == nullptr) {
5490 PrintInvalidParamError(js, param: "breakpointId");
5491 return;
5492 }
5493 if (isolate->debugger()->SetBreakpointState(bpt, enable)) {
5494 if (Service::debug_stream.enabled()) {
5495 ServiceEvent event(isolate, ServiceEvent::kBreakpointUpdated);
5496 event.set_breakpoint(bpt);
5497 Service::HandleEvent(event: &event);
5498 }
5499 }
5500 bpt->PrintJSON(stream: js);
5501}
5502
5503static const MethodParameter* const get_flag_list_params[] = {
5504 NO_ISOLATE_PARAMETER,
5505 nullptr,
5506};
5507
5508static void GetFlagList(Thread* thread, JSONStream* js) {
5509 Flags::PrintJSON(js);
5510}
5511
5512static const MethodParameter* const set_flags_params[] = {
5513 NO_ISOLATE_PARAMETER,
5514 nullptr,
5515};
5516
5517static void SetFlag(Thread* thread, JSONStream* js) {
5518 const char* flag_name = js->LookupParam(key: "name");
5519 if (flag_name == nullptr) {
5520 PrintMissingParamError(js, param: "name");
5521 return;
5522 }
5523 const char* flag_value = js->LookupParam(key: "value");
5524 if (flag_value == nullptr) {
5525 PrintMissingParamError(js, param: "value");
5526 return;
5527 }
5528
5529 if (Flags::Lookup(name: flag_name) == nullptr) {
5530 JSONObject jsobj(js);
5531 jsobj.AddProperty(name: "type", s: "Error");
5532 jsobj.AddProperty(name: "message", s: "Cannot set flag: flag not found");
5533 return;
5534 }
5535
5536 // Changing most flags at runtime is dangerous because e.g., it may leave the
5537 // behavior generated code and the runtime out of sync.
5538 const uintptr_t kProfilePeriodIndex = 3;
5539 const uintptr_t kProfilerIndex = 4;
5540 const char* kAllowedFlags[] = {
5541 "pause_isolates_on_start",
5542 "pause_isolates_on_exit",
5543 "pause_isolates_on_unhandled_exceptions",
5544 "profile_period",
5545 "profiler",
5546 };
5547
5548 bool allowed = false;
5549 bool profile_period = false;
5550 bool profiler = false;
5551 for (size_t i = 0; i < ARRAY_SIZE(kAllowedFlags); i++) {
5552 if (strcmp(flag_name, kAllowedFlags[i]) == 0) {
5553 allowed = true;
5554 profile_period = (i == kProfilePeriodIndex);
5555 profiler = (i == kProfilerIndex);
5556 break;
5557 }
5558 }
5559
5560 if (!allowed) {
5561 JSONObject jsobj(js);
5562 jsobj.AddProperty(name: "type", s: "Error");
5563 jsobj.AddProperty(name: "message", s: "Cannot set flag: cannot change at runtime");
5564 return;
5565 }
5566
5567 const char* error = nullptr;
5568 if (Flags::SetFlag(name: flag_name, value: flag_value, error: &error)) {
5569 PrintSuccess(js);
5570 if (profile_period) {
5571 // FLAG_profile_period has already been set to the new value. Now we need
5572 // to notify the ThreadInterrupter to pick up the change.
5573 Profiler::UpdateSamplePeriod();
5574 } else if (profiler) {
5575 // FLAG_profiler has already been set to the new value.
5576 Profiler::UpdateRunningState();
5577 }
5578 if (Service::vm_stream.enabled()) {
5579 ServiceEvent event(ServiceEvent::kVMFlagUpdate);
5580 event.set_flag_name(flag_name);
5581 event.set_flag_new_value(flag_value);
5582 Service::HandleEvent(event: &event);
5583 }
5584 } else {
5585 JSONObject jsobj(js);
5586 jsobj.AddProperty(name: "type", s: "Error");
5587 jsobj.AddProperty(name: "message", s: error);
5588 }
5589}
5590
5591static const MethodParameter* const set_library_debuggable_params[] = {
5592 RUNNABLE_ISOLATE_PARAMETER,
5593 new IdParameter("libraryId", true),
5594 new BoolParameter("isDebuggable", true),
5595 nullptr,
5596};
5597
5598static void SetLibraryDebuggable(Thread* thread, JSONStream* js) {
5599 const char* lib_id = js->LookupParam(key: "libraryId");
5600 ObjectIdRing::LookupResult lookup_result;
5601 Object& obj =
5602 Object::Handle(ptr: LookupHeapObject(thread, id_original: lib_id, result: &lookup_result));
5603 const bool is_debuggable =
5604 BoolParameter::Parse(value: js->LookupParam(key: "isDebuggable"), default_value: false);
5605 if (obj.IsLibrary()) {
5606 const Library& lib = Library::Cast(obj);
5607 lib.set_debuggable(is_debuggable);
5608 PrintSuccess(js);
5609 return;
5610 }
5611 PrintInvalidParamError(js, param: "libraryId");
5612}
5613
5614static const MethodParameter* const set_name_params[] = {
5615 ISOLATE_PARAMETER,
5616 new MethodParameter("name", true),
5617 nullptr,
5618};
5619
5620static void SetName(Thread* thread, JSONStream* js) {
5621 Isolate* isolate = thread->isolate();
5622 isolate->set_name(js->LookupParam(key: "name"));
5623 if (Service::isolate_stream.enabled()) {
5624 ServiceEvent event(isolate, ServiceEvent::kIsolateUpdate);
5625 Service::HandleEvent(event: &event);
5626 }
5627 PrintSuccess(js);
5628 return;
5629}
5630
5631static const MethodParameter* const set_vm_name_params[] = {
5632 NO_ISOLATE_PARAMETER,
5633 new MethodParameter("name", true),
5634 nullptr,
5635};
5636
5637static void SetVMName(Thread* thread, JSONStream* js) {
5638 const char* name_param = js->LookupParam(key: "name");
5639 free(vm_name);
5640 vm_name = Utils::StrDup(s: name_param);
5641 if (Service::vm_stream.enabled()) {
5642 ServiceEvent event(ServiceEvent::kVMUpdate);
5643 Service::HandleEvent(event: &event);
5644 }
5645 PrintSuccess(js);
5646 return;
5647}
5648
5649static const MethodParameter* const set_trace_class_allocation_params[] = {
5650 RUNNABLE_ISOLATE_PARAMETER,
5651 new IdParameter("classId", true),
5652 new BoolParameter("enable", true),
5653 nullptr,
5654};
5655
5656static void SetTraceClassAllocation(Thread* thread, JSONStream* js) {
5657 if (CheckCompilerDisabled(thread, js)) {
5658 return;
5659 }
5660
5661 const char* class_id = js->LookupParam(key: "classId");
5662 const bool enable = BoolParameter::Parse(value: js->LookupParam(key: "enable"));
5663 intptr_t cid = -1;
5664 GetPrefixedIntegerId(s: class_id, prefix: "classes/", service_id: &cid);
5665 Isolate* isolate = thread->isolate();
5666 if (!IsValidClassId(isolate, cid)) {
5667 PrintInvalidParamError(js, param: "classId");
5668 return;
5669 }
5670 const Class& cls = Class::Handle(ptr: GetClassForId(isolate, cid));
5671 ASSERT(!cls.IsNull());
5672 cls.SetTraceAllocation(enable);
5673 PrintSuccess(js);
5674}
5675
5676static const MethodParameter* const get_default_classes_aliases_params[] = {
5677 NO_ISOLATE_PARAMETER,
5678 nullptr,
5679};
5680
5681static void GetDefaultClassesAliases(Thread* thread, JSONStream* js) {
5682 JSONObject jsobj(js);
5683 jsobj.AddProperty(name: "type", s: "ClassesAliasesMap");
5684
5685 JSONObject map(&jsobj, "map");
5686
5687#define DEFINE_ADD_VALUE_F(id) \
5688 internals.AddValueF("classes/%" Pd, static_cast<intptr_t>(id));
5689#define DEFINE_ADD_VALUE_F_CID(clazz) DEFINE_ADD_VALUE_F(k##clazz##Cid)
5690 {
5691 JSONArray internals(&map, "<VM Internals>");
5692 for (intptr_t id = kFirstInternalOnlyCid; id <= kLastInternalOnlyCid;
5693 ++id) {
5694 DEFINE_ADD_VALUE_F(id);
5695 }
5696 DEFINE_ADD_VALUE_F_CID(LibraryPrefix);
5697 }
5698 {
5699 JSONArray internals(&map, "Type");
5700 for (intptr_t id = kAbstractTypeCid; id <= kTypeParameterCid; ++id) {
5701 DEFINE_ADD_VALUE_F(id);
5702 }
5703 }
5704 {
5705 JSONArray internals(&map, "Object");
5706 DEFINE_ADD_VALUE_F_CID(Instance);
5707 }
5708 {
5709 JSONArray internals(&map, "Closure");
5710 DEFINE_ADD_VALUE_F_CID(Closure);
5711 DEFINE_ADD_VALUE_F_CID(Context);
5712 }
5713 {
5714 JSONArray internals(&map, "Int");
5715 for (intptr_t id = kIntegerCid; id <= kMintCid; ++id) {
5716 DEFINE_ADD_VALUE_F(id);
5717 }
5718 }
5719 {
5720 JSONArray internals(&map, "Double");
5721 DEFINE_ADD_VALUE_F_CID(Double);
5722 }
5723 {
5724 JSONArray internals(&map, "String");
5725 CLASS_LIST_STRINGS(DEFINE_ADD_VALUE_F_CID)
5726 }
5727 {
5728 JSONArray internals(&map, "List");
5729 CLASS_LIST_ARRAYS(DEFINE_ADD_VALUE_F_CID)
5730 DEFINE_ADD_VALUE_F_CID(ByteBuffer)
5731 }
5732 {
5733 JSONArray internals(&map, "Map");
5734 CLASS_LIST_MAPS(DEFINE_ADD_VALUE_F_CID)
5735 }
5736
5737 {
5738 JSONArray internals(&map, "Set");
5739 CLASS_LIST_SETS(DEFINE_ADD_VALUE_F_CID)
5740 }
5741#define DEFINE_ADD_MAP_KEY(clazz) \
5742 { \
5743 JSONArray internals(&map, #clazz); \
5744 DEFINE_ADD_VALUE_F_CID(TypedData##clazz) \
5745 DEFINE_ADD_VALUE_F_CID(TypedData##clazz##View) \
5746 DEFINE_ADD_VALUE_F_CID(ExternalTypedData##clazz) \
5747 DEFINE_ADD_VALUE_F_CID(UnmodifiableTypedData##clazz##View) \
5748 }
5749 CLASS_LIST_TYPED_DATA(DEFINE_ADD_MAP_KEY)
5750#undef DEFINE_ADD_MAP_KEY
5751#define DEFINE_ADD_MAP_KEY(clazz) \
5752 { \
5753 JSONArray internals(&map, #clazz); \
5754 DEFINE_ADD_VALUE_F_CID(Ffi##clazz) \
5755 }
5756 CLASS_LIST_FFI(DEFINE_ADD_MAP_KEY)
5757#undef DEFINE_ADD_MAP_KEY
5758#undef DEFINE_ADD_VALUE_F_CID
5759#undef DEFINE_ADD_VALUE_F
5760}
5761
5762// clang-format off
5763static const ServiceMethodDescriptor service_methods_[] = {
5764 { .name: "_echo", .entry: Echo,
5765 .parameters: nullptr },
5766 { .name: "_respondWithMalformedJson", .entry: RespondWithMalformedJson,
5767 .parameters: nullptr },
5768 { .name: "_respondWithMalformedObject", .entry: RespondWithMalformedObject,
5769 .parameters: nullptr },
5770 { .name: "_triggerEchoEvent", .entry: TriggerEchoEvent,
5771 .parameters: nullptr },
5772 { .name: "addBreakpoint", .entry: AddBreakpoint,
5773 .parameters: add_breakpoint_params },
5774 { .name: "addBreakpointWithScriptUri", .entry: AddBreakpointWithScriptUri,
5775 .parameters: add_breakpoint_with_script_uri_params },
5776 { .name: "addBreakpointAtEntry", .entry: AddBreakpointAtEntry,
5777 .parameters: add_breakpoint_at_entry_params },
5778 { .name: "_addBreakpointAtActivation", .entry: AddBreakpointAtActivation,
5779 .parameters: add_breakpoint_at_activation_params },
5780 { .name: "_buildExpressionEvaluationScope", .entry: BuildExpressionEvaluationScope,
5781 .parameters: build_expression_evaluation_scope_params },
5782 { .name: "clearCpuSamples", .entry: ClearCpuSamples,
5783 .parameters: clear_cpu_samples_params },
5784 { .name: "clearVMTimeline", .entry: ClearVMTimeline,
5785 .parameters: clear_vm_timeline_params, },
5786 { .name: "_compileExpression", .entry: CompileExpression, .parameters: compile_expression_params },
5787 { .name: "_enableProfiler", .entry: EnableProfiler,
5788 .parameters: enable_profiler_params, },
5789 { .name: "evaluate", .entry: Evaluate,
5790 .parameters: evaluate_params },
5791 { .name: "evaluateInFrame", .entry: EvaluateInFrame,
5792 .parameters: evaluate_in_frame_params },
5793 { .name: "_getAllocationProfile", .entry: GetAllocationProfile,
5794 .parameters: get_allocation_profile_params },
5795 { .name: "getAllocationProfile", .entry: GetAllocationProfilePublic,
5796 .parameters: get_allocation_profile_params },
5797 { .name: "getAllocationTraces", .entry: GetAllocationTraces,
5798 .parameters: get_allocation_traces_params },
5799 { .name: "getClassList", .entry: GetClassList,
5800 .parameters: get_class_list_params },
5801 { .name: "getCpuSamples", .entry: GetCpuSamples,
5802 .parameters: get_cpu_samples_params },
5803 { .name: "getFlagList", .entry: GetFlagList,
5804 .parameters: get_flag_list_params },
5805 { .name: "_getHeapMap", .entry: GetHeapMap,
5806 .parameters: get_heap_map_params },
5807 { .name: "_getImplementationFields", .entry: GetImplementationFields,
5808 .parameters: get_implementation_fields_params },
5809 { .name: "getInboundReferences", .entry: GetInboundReferences,
5810 .parameters: get_inbound_references_params },
5811 { .name: "getInstances", .entry: GetInstances,
5812 .parameters: get_instances_params },
5813 { .name: "getInstancesAsList", .entry: GetInstancesAsList,
5814 .parameters: get_instances_as_list_params },
5815#if defined(SUPPORT_PERFETTO)
5816 { .name: "getPerfettoCpuSamples", .entry: GetPerfettoCpuSamples,
5817 .parameters: get_cpu_samples_params },
5818 { .name: "getPerfettoVMTimeline", .entry: GetPerfettoVMTimeline,
5819 .parameters: get_vm_timeline_params },
5820#endif // defined(SUPPORT_PERFETTO)
5821 { .name: "getPorts", .entry: GetPorts,
5822 .parameters: get_ports_params },
5823 { .name: "getIsolate", .entry: GetIsolate,
5824 .parameters: get_isolate_params },
5825 { .name: "_getIsolateObjectStore", .entry: GetIsolateObjectStore,
5826 .parameters: get_isolate_object_store_params },
5827 { .name: "getIsolateGroup", .entry: GetIsolateGroup,
5828 .parameters: get_isolate_group_params },
5829 { .name: "getMemoryUsage", .entry: GetMemoryUsage,
5830 .parameters: get_memory_usage_params },
5831 { .name: "getIsolateGroupMemoryUsage", .entry: GetIsolateGroupMemoryUsage,
5832 .parameters: get_isolate_group_memory_usage_params },
5833 { .name: "_getIsolateMetric", .entry: GetIsolateMetric,
5834 .parameters: get_isolate_metric_params },
5835 { .name: "_getIsolateMetricList", .entry: GetIsolateMetricList,
5836 .parameters: get_isolate_metric_list_params },
5837 { .name: "getIsolatePauseEvent", .entry: GetIsolatePauseEvent,
5838 .parameters: get_isolate_pause_event_params },
5839 { .name: "getObject", .entry: GetObject,
5840 .parameters: get_object_params },
5841 { .name: "_getObjectStore", .entry: GetObjectStore,
5842 .parameters: get_object_store_params },
5843 { .name: "_getPersistentHandles", .entry: GetPersistentHandles,
5844 .parameters: get_persistent_handles_params, },
5845 { .name: "_getPorts", .entry: GetPortsPrivate,
5846 .parameters: get_ports_private_params },
5847 { .name: "getProcessMemoryUsage", .entry: GetProcessMemoryUsage,
5848 .parameters: get_process_memory_usage_params },
5849 { .name: "_getReachableSize", .entry: GetReachableSize,
5850 .parameters: get_reachable_size_params },
5851 { .name: "_getRetainedSize", .entry: GetRetainedSize,
5852 .parameters: get_retained_size_params },
5853 { .name: "lookupResolvedPackageUris", .entry: LookupResolvedPackageUris,
5854 .parameters: lookup_resolved_package_uris_params },
5855 { .name: "lookupPackageUris", .entry: LookupPackageUris,
5856 .parameters: lookup_package_uris_params },
5857 { .name: "getRetainingPath", .entry: GetRetainingPath,
5858 .parameters: get_retaining_path_params },
5859 { .name: "getScripts", .entry: GetScripts,
5860 .parameters: get_scripts_params },
5861 { .name: "getSourceReport", .entry: GetSourceReport,
5862 .parameters: get_source_report_params },
5863 { .name: "getStack", .entry: GetStack,
5864 .parameters: get_stack_params },
5865 { .name: "_getTagProfile", .entry: GetTagProfile,
5866 .parameters: get_tag_profile_params },
5867 { .name: "_getTypeArgumentsList", .entry: GetTypeArgumentsList,
5868 .parameters: get_type_arguments_list_params },
5869 { .name: "getVersion", .entry: GetVersion,
5870 .parameters: get_version_params },
5871 { .name: "getVM", .entry: GetVM,
5872 .parameters: get_vm_params },
5873 { .name: "getVMTimeline", .entry: GetVMTimeline,
5874 .parameters: get_vm_timeline_params },
5875 { .name: "getVMTimelineFlags", .entry: GetVMTimelineFlags,
5876 .parameters: get_vm_timeline_flags_params },
5877 { .name: "getVMTimelineMicros", .entry: GetVMTimelineMicros,
5878 .parameters: get_vm_timeline_micros_params },
5879 { .name: "invoke", .entry: Invoke, .parameters: invoke_params },
5880 { .name: "kill", .entry: Kill, .parameters: kill_params },
5881 { .name: "pause", .entry: Pause,
5882 .parameters: pause_params },
5883 { .name: "removeBreakpoint", .entry: RemoveBreakpoint,
5884 .parameters: remove_breakpoint_params },
5885 { .name: "reloadSources", .entry: ReloadSources,
5886 .parameters: reload_sources_params },
5887 { .name: "_reloadSources", .entry: ReloadSources,
5888 .parameters: reload_sources_params },
5889 { .name: "resume", .entry: Resume,
5890 .parameters: resume_params },
5891 { .name: "requestHeapSnapshot", .entry: RequestHeapSnapshot,
5892 .parameters: request_heap_snapshot_params },
5893 { .name: "_evaluateCompiledExpression", .entry: EvaluateCompiledExpression,
5894 .parameters: evaluate_compiled_expression_params },
5895 { .name: "setBreakpointState", .entry: SetBreakpointState,
5896 .parameters: set_breakpoint_state_params },
5897 { .name: "setExceptionPauseMode", .entry: SetExceptionPauseMode,
5898 .parameters: set_exception_pause_mode_params },
5899 { .name: "setIsolatePauseMode", .entry: SetIsolatePauseMode,
5900 .parameters: set_isolate_pause_mode_params },
5901 { .name: "setFlag", .entry: SetFlag,
5902 .parameters: set_flags_params },
5903 { .name: "setLibraryDebuggable", .entry: SetLibraryDebuggable,
5904 .parameters: set_library_debuggable_params },
5905 { .name: "setName", .entry: SetName,
5906 .parameters: set_name_params },
5907 { .name: "_setStreamIncludePrivateMembers", .entry: SetStreamIncludePrivateMembers,
5908 .parameters: set_stream_include_private_members_params },
5909 { .name: "setTraceClassAllocation", .entry: SetTraceClassAllocation,
5910 .parameters: set_trace_class_allocation_params },
5911 { .name: "setVMName", .entry: SetVMName,
5912 .parameters: set_vm_name_params },
5913 { .name: "setVMTimelineFlags", .entry: SetVMTimelineFlags,
5914 .parameters: set_vm_timeline_flags_params },
5915 { .name: "_collectAllGarbage", .entry: CollectAllGarbage,
5916 .parameters: collect_all_garbage_params },
5917 { .name: "_getDefaultClassesAliases", .entry: GetDefaultClassesAliases,
5918 .parameters: get_default_classes_aliases_params },
5919};
5920// clang-format on
5921
5922const ServiceMethodDescriptor* FindMethod(const char* method_name) {
5923 intptr_t num_methods = sizeof(service_methods_) / sizeof(service_methods_[0]);
5924 for (intptr_t i = 0; i < num_methods; i++) {
5925 const ServiceMethodDescriptor& method = service_methods_[i];
5926 if (strcmp(method_name, method.name) == 0) {
5927 return &method;
5928 }
5929 }
5930 return nullptr;
5931}
5932
5933#endif // !PRODUCT
5934
5935} // namespace dart
5936

source code of dart_sdk/runtime/vm/service.cc