| Index: runtime/vm/service.cc
|
| diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc
|
| index 4735b51036403ae329e2e5fc0c99c7b2f773dbc1..35be737034cc8e4dad41d78a6a61baa1475a0c90 100644
|
| --- a/runtime/vm/service.cc
|
| +++ b/runtime/vm/service.cc
|
| @@ -30,6 +30,7 @@
|
| #include "vm/reusable_handles.h"
|
| #include "vm/service_event.h"
|
| #include "vm/service_isolate.h"
|
| +#include "vm/source_report.h"
|
| #include "vm/stack_frame.h"
|
| #include "vm/symbols.h"
|
| #include "vm/unicode.h"
|
| @@ -561,6 +562,125 @@ T EnumMapper(const char* value, const char* const* enums, T* values) {
|
| }
|
|
|
|
|
| +class EnumListParameter : public MethodParameter {
|
| + public:
|
| + EnumListParameter(const char* name, bool required, const char* const* enums)
|
| + : MethodParameter(name, required),
|
| + enums_(enums) {
|
| + }
|
| +
|
| + virtual bool Validate(const char* value) const {
|
| + return ElementCount(value) >= 0;
|
| + }
|
| +
|
| + const char** Parse(Zone* zone, const char* value_in) const {
|
| + const char* kJsonChars = " \t\r\n[,]";
|
| +
|
| + // Make a writeable copy of the value.
|
| + char* value = zone->MakeCopyOfString(value_in);
|
| + intptr_t element_count = ElementCount(value);
|
| + intptr_t element_pos = 0;
|
| +
|
| + // Allocate our element array. +1 for NULL terminator.
|
| + char** elements = zone->Alloc<char*>(element_count + 1);
|
| + elements[element_count] = NULL;
|
| +
|
| + // Parse the string destructively. Build the list of elements.
|
| + while (element_pos < element_count) {
|
| + // Skip to the next element.
|
| + value += strspn(value, kJsonChars);
|
| +
|
| + intptr_t len = strcspn(value, kJsonChars);
|
| + ASSERT(len > 0); // We rely on the parameter being validated already.
|
| + value[len] = '\0';
|
| + elements[element_pos++] = value;
|
| +
|
| + // Advance. +1 for null terminator.
|
| + value += (len + 1);
|
| + }
|
| + return const_cast<const char**>(elements);
|
| + }
|
| +
|
| + private:
|
| + // For now observatory enums are ascii letters plus underscore.
|
| + static bool IsEnumChar(char c) {
|
| + return (((c >= 'a') && (c <= 'z')) ||
|
| + ((c >= 'A') && (c <= 'Z')) ||
|
| + (c == '_'));
|
| + }
|
| +
|
| + // Returns number of elements in the list. -1 on parse error.
|
| + intptr_t ElementCount(const char* value) const {
|
| + const char* kJsonWhitespaceChars = " \t\r\n";
|
| +
|
| + if (value == NULL) {
|
| + return -1;
|
| + }
|
| + const char* cp = value;
|
| + cp += strspn(cp, kJsonWhitespaceChars);
|
| + if (*cp++ != '[') {
|
| + // Missing initial [.
|
| + return -1;
|
| + }
|
| + bool closed = false;
|
| + bool element_allowed = true;
|
| + intptr_t element_count = 0;
|
| + while (true) {
|
| + // Skip json whitespace.
|
| + cp += strspn(cp, kJsonWhitespaceChars);
|
| + switch (*cp) {
|
| + case '\0':
|
| + return closed ? element_count : -1;
|
| + case ']':
|
| + closed = true;
|
| + cp++;
|
| + break;
|
| + case ',':
|
| + if (element_allowed) {
|
| + return -1;
|
| + }
|
| + element_allowed = true;
|
| + cp++;
|
| + break;
|
| + default:
|
| + if (!element_allowed) {
|
| + return -1;
|
| + }
|
| + bool valid_enum = false;
|
| + if (enums_ != NULL) {
|
| + for (intptr_t i = 0; enums_[i] != NULL; i++) {
|
| + intptr_t len = strlen(enums_[i]);
|
| + if (strncmp(cp, enums_[i], len) == 0) {
|
| + element_count++;
|
| + valid_enum = true;
|
| + cp += len;
|
| + element_allowed = false; // we need a comma first.
|
| + break;
|
| + }
|
| + }
|
| + } else {
|
| + // Allow any identifiers
|
| + const char* id_start = cp;
|
| + while (IsEnumChar(*cp)) {
|
| + cp++;
|
| + }
|
| + if (cp == id_start) {
|
| + // Empty identifier, something like this [,].
|
| + return -1;
|
| + }
|
| + }
|
| + if (!valid_enum) {
|
| + return -1;
|
| + }
|
| + break;
|
| + }
|
| + }
|
| + }
|
| +
|
| + const char* const* enums_;
|
| +};
|
| +
|
| +
|
| typedef bool (*ServiceMethodEntry)(Thread* thread, JSONStream* js);
|
|
|
|
|
| @@ -2191,6 +2311,80 @@ static bool GetCoverage(Thread* thread, JSONStream* js) {
|
| }
|
|
|
|
|
| +static const char* kCallSitesStr = "CallSites";
|
| +static const char* kCoverageStr = "Coverage";
|
| +
|
| +
|
| +static const char* const report_enum_names[] = {
|
| + kCallSitesStr,
|
| + kCoverageStr,
|
| + NULL,
|
| +};
|
| +
|
| +
|
| +static const EnumListParameter* reports_parameter =
|
| + new EnumListParameter("reports", true, report_enum_names);
|
| +
|
| +
|
| +static const MethodParameter* get_source_report_params[] = {
|
| + ISOLATE_PARAMETER,
|
| + reports_parameter,
|
| + new IdParameter("scriptId", false),
|
| + new UIntParameter("tokenPos", false),
|
| + new UIntParameter("endTokenPos", false),
|
| + NULL,
|
| +};
|
| +
|
| +
|
| +static bool GetSourceReport(Thread* thread, JSONStream* js) {
|
| + const char* reports_str = js->LookupParam("reports");
|
| + const char** reports = reports_parameter->Parse(thread->zone(), reports_str);
|
| + intptr_t report_set = 0;
|
| + while (*reports != NULL) {
|
| + if (strcmp(*reports, kCallSitesStr) == 0) {
|
| + report_set |= SourceReport::kCallSites;
|
| + } else if (strcmp(*reports, kCoverageStr) == 0) {
|
| + report_set |= SourceReport::kCoverage;
|
| + }
|
| + reports++;
|
| + }
|
| +
|
| + Script& script = Script::Handle();
|
| + intptr_t start_pos = UIntParameter::Parse(js->LookupParam("tokenPos"));
|
| + intptr_t end_pos = UIntParameter::Parse(js->LookupParam("endTokenPos"));
|
| +
|
| + if (js->HasParam("scriptId")) {
|
| + // Get the target script.
|
| + const char* script_id_param = js->LookupParam("scriptId");
|
| + const Object& obj =
|
| + Object::Handle(LookupHeapObject(thread, script_id_param, NULL));
|
| + if (obj.raw() == Object::sentinel().raw() || !obj.IsScript()) {
|
| + PrintInvalidParamError(js, "scriptId");
|
| + return true;
|
| + }
|
| + script ^= obj.raw();
|
| + } else {
|
| + if (js->HasParam("tokenPos")) {
|
| + js->PrintError(
|
| + kInvalidParams,
|
| + "%s: the 'tokenPos' parameter requires the 'scriptId' parameter",
|
| + js->method());
|
| + return true;
|
| + }
|
| + if (js->HasParam("endTokenPos")) {
|
| + js->PrintError(
|
| + kInvalidParams,
|
| + "%s: the 'endTokenPos' parameter requires the 'scriptId' parameter",
|
| + js->method());
|
| + return true;
|
| + }
|
| + }
|
| + SourceReport report(report_set);
|
| + report.PrintJSON(js, script, start_pos, end_pos);
|
| + return true;
|
| +}
|
| +
|
| +
|
| static const MethodParameter* get_call_site_data_params[] = {
|
| ISOLATE_PARAMETER,
|
| new IdParameter("targetId", false),
|
| @@ -3608,6 +3802,8 @@ static const ServiceMethodDescriptor service_methods_[] = {
|
| get_retained_size_params },
|
| { "_getRetainingPath", GetRetainingPath,
|
| get_retaining_path_params },
|
| + { "_getSourceReport", GetSourceReport,
|
| + get_source_report_params },
|
| { "getStack", GetStack,
|
| get_stack_params },
|
| { "_getTagProfile", GetTagProfile,
|
|
|