e2df4d30a0
Instead, split each old use into the following cases: * If the TokenPosition value is expected to be a real token position, then use TokenPosition::Pos(). * If the TokenPosition is being serialized in some way, then use TokenPosition::Serialize() and change the place where the TokenPosition is recreated to use TokenPosition::Deserialize(). * If the value of the TokenPosition is being printed for debugging purposes, then just use TokenPosition::ToCString() instead. That is, we try to pin down when token positions are expected to be real vs. when other types of token positions can be found. Another source of possible error when using token positions is to convert between synthetic and real token positions. In the past, synthetic token positions may have been based off real token positions, but that is no longer the case. Thus, all methods that allow that conversion have been removed, and instead there is a new static method for constructing synthetic tokens from valid nonces. This CL also makes it so that Pos() and relational operators on token positions are only defined on real token positions, to avoid any assumptions about what the value encoded in synthetic positions mean. To help with cases where non-real token positions may occur, four helper methods are added: * TokenPosition::Min(a, b): A static method that returns the smallest real token position provided. If neither `a` or `b` are real, returns `a`. * TokenPosition::Max(a, b): A static method that returns the largest real token position provided. If neither `a` or `b` are real, returns `a`. * TokenPosition::IsWithin(start, end): Determines whether `this` falls between `start` and `end` (inclusive). If `this` is non-real, then it must be either `start` or `end` if synthetic, otherwise false. Otherwise, we mimic the old style of range checking, which means that non-real starts and ends are treated as less than every real token. * TokenPosition::CompareForSorting(other): Unlike the relational operators, provides a comparison between any types of token positions for purposes such as sorting. Currently only used in the profiler. It also changes TokenPosition::ToCString() to tag synthetic token positions, so they can be distinguished from real ones at a glance. TEST=Existing test suite on trybots, especially the observatory tests which make heavy use of the debugger and the unit tests for the profiler/source report modules. Bug: https://github.com/dart-lang/sdk/issues/44436 Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-nnbd-linux-debug-x64-try,vm-kernel-precomp-linux-debug-x64-try,vm-kernel-nnbd-linux-debug-x64-try,vm-kernel-linux-debug-x64-try,vm-kernel-linux-release-x64-try,vm-kernel-nnbd-linux-release-x64-try,vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-nnbd-linux-release-x64-try,vm-kernel-linux-product-x64-try,vm-kernel-precomp-linux-product-x64-try Change-Id: Ic06aa0bc7a1f0fbac7257ed22ca5e7e0ccd7f3f2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/174924 Commit-Queue: Tess Strickland <sstrickl@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Reviewed-by: Daco Harkes <dacoharkes@google.com>
564 lines
16 KiB
C++
564 lines
16 KiB
C++
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
#include "platform/assert.h"
|
|
|
|
#include "include/dart_native_api.h"
|
|
#include "platform/unicode.h"
|
|
#include "vm/dart_entry.h"
|
|
#include "vm/debugger.h"
|
|
#include "vm/heap/safepoint.h"
|
|
#include "vm/json_stream.h"
|
|
#include "vm/message.h"
|
|
#include "vm/metrics.h"
|
|
#include "vm/object.h"
|
|
#include "vm/service.h"
|
|
#include "vm/service_event.h"
|
|
#include "vm/thread_registry.h"
|
|
#include "vm/timeline.h"
|
|
|
|
namespace dart {
|
|
|
|
#ifndef PRODUCT
|
|
|
|
DECLARE_FLAG(bool, trace_service);
|
|
|
|
JSONStream::JSONStream(intptr_t buf_size)
|
|
: writer_(buf_size),
|
|
default_id_zone_(),
|
|
id_zone_(&default_id_zone_),
|
|
reply_port_(ILLEGAL_PORT),
|
|
seq_(NULL),
|
|
parameter_keys_(NULL),
|
|
parameter_values_(NULL),
|
|
method_(""),
|
|
param_keys_(NULL),
|
|
param_values_(NULL),
|
|
num_params_(0),
|
|
offset_(0),
|
|
count_(-1) {
|
|
ObjectIdRing* ring = NULL;
|
|
Isolate* isolate = Isolate::Current();
|
|
if (isolate != NULL) {
|
|
ring = isolate->EnsureObjectIdRing();
|
|
}
|
|
default_id_zone_.Init(ring, ObjectIdRing::kAllocateId);
|
|
}
|
|
|
|
void JSONStream::Setup(Zone* zone,
|
|
Dart_Port reply_port,
|
|
const Instance& seq,
|
|
const String& method,
|
|
const Array& param_keys,
|
|
const Array& param_values,
|
|
bool parameters_are_dart_objects) {
|
|
set_reply_port(reply_port);
|
|
seq_ = &Instance::ZoneHandle(seq.raw());
|
|
method_ = method.ToCString();
|
|
|
|
if (parameters_are_dart_objects) {
|
|
parameter_keys_ = &Array::ZoneHandle(param_keys.raw());
|
|
parameter_values_ = &Array::ZoneHandle(param_values.raw());
|
|
ASSERT(parameter_keys_->Length() == parameter_values_->Length());
|
|
} else if (param_keys.Length() > 0) {
|
|
String& string_iterator = String::Handle();
|
|
ASSERT(param_keys.Length() == param_values.Length());
|
|
const char** param_keys_native =
|
|
zone->Alloc<const char*>(param_keys.Length());
|
|
const char** param_values_native =
|
|
zone->Alloc<const char*>(param_keys.Length());
|
|
for (intptr_t i = 0; i < param_keys.Length(); i++) {
|
|
string_iterator ^= param_keys.At(i);
|
|
param_keys_native[i] =
|
|
zone->MakeCopyOfString(string_iterator.ToCString());
|
|
string_iterator ^= param_values.At(i);
|
|
param_values_native[i] =
|
|
zone->MakeCopyOfString(string_iterator.ToCString());
|
|
}
|
|
SetParams(param_keys_native, param_values_native, param_keys.Length());
|
|
}
|
|
|
|
if (FLAG_trace_service) {
|
|
Isolate* isolate = Isolate::Current();
|
|
ASSERT(isolate != NULL);
|
|
int64_t main_port = static_cast<int64_t>(isolate->main_port());
|
|
const char* isolate_name = isolate->name();
|
|
setup_time_micros_ = OS::GetCurrentTimeMicros();
|
|
OS::PrintErr("[+%" Pd64 "ms] Isolate (%" Pd64
|
|
") %s processing service "
|
|
"request %s\n",
|
|
Dart::UptimeMillis(), main_port, isolate_name, method_);
|
|
}
|
|
buffer()->Printf("{\"jsonrpc\":\"2.0\", \"result\":");
|
|
}
|
|
|
|
void JSONStream::SetupError() {
|
|
Clear();
|
|
buffer()->Printf("{\"jsonrpc\":\"2.0\", \"error\":");
|
|
}
|
|
|
|
static const char* GetJSONRpcErrorMessage(intptr_t code) {
|
|
switch (code) {
|
|
case kParseError:
|
|
return "Parse error";
|
|
case kInvalidRequest:
|
|
return "Invalid Request";
|
|
case kMethodNotFound:
|
|
return "Method not found";
|
|
case kInvalidParams:
|
|
return "Invalid params";
|
|
case kInternalError:
|
|
return "Internal error";
|
|
case kFeatureDisabled:
|
|
return "Feature is disabled";
|
|
case kCannotAddBreakpoint:
|
|
return "Cannot add breakpoint";
|
|
case kStreamAlreadySubscribed:
|
|
return "Stream already subscribed";
|
|
case kStreamNotSubscribed:
|
|
return "Stream not subscribed";
|
|
case kIsolateMustBeRunnable:
|
|
return "Isolate must be runnable";
|
|
case kIsolateMustBePaused:
|
|
return "Isolate must be paused";
|
|
case kCannotResume:
|
|
return "Cannot resume execution";
|
|
case kIsolateIsReloading:
|
|
return "Isolate is reloading";
|
|
case kIsolateReloadBarred:
|
|
return "Isolate cannot be reloaded";
|
|
case kIsolateMustHaveReloaded:
|
|
return "Isolate must have reloaded";
|
|
case kFileSystemAlreadyExists:
|
|
return "File system already exists";
|
|
case kFileSystemDoesNotExist:
|
|
return "File system does not exist";
|
|
case kFileDoesNotExist:
|
|
return "File does not exist";
|
|
case kInvalidTimelineRequest:
|
|
return "The timeline related request could not be completed due to the "
|
|
"current configuration";
|
|
default:
|
|
return "Extension error";
|
|
}
|
|
}
|
|
|
|
static void PrintRequest(JSONObject* obj, JSONStream* js) {
|
|
JSONObject jsobj(obj, "request");
|
|
jsobj.AddProperty("method", js->method());
|
|
{
|
|
JSONObject params(&jsobj, "params");
|
|
for (intptr_t i = 0; i < js->num_params(); i++) {
|
|
params.AddProperty(js->GetParamKey(i), js->GetParamValue(i));
|
|
}
|
|
}
|
|
}
|
|
|
|
void JSONStream::PrintError(intptr_t code, const char* details_format, ...) {
|
|
SetupError();
|
|
JSONObject jsobj(this);
|
|
jsobj.AddProperty("code", code);
|
|
jsobj.AddProperty("message", GetJSONRpcErrorMessage(code));
|
|
{
|
|
JSONObject data(&jsobj, "data");
|
|
PrintRequest(&data, this);
|
|
if (details_format != NULL) {
|
|
va_list args;
|
|
va_start(args, details_format);
|
|
intptr_t len = Utils::VSNPrint(NULL, 0, details_format, args);
|
|
va_end(args);
|
|
|
|
char* buffer = Thread::Current()->zone()->Alloc<char>(len + 1);
|
|
va_list args2;
|
|
va_start(args2, details_format);
|
|
Utils::VSNPrint(buffer, (len + 1), details_format, args2);
|
|
va_end(args2);
|
|
data.AddProperty("details", buffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
void JSONStream::PostNullReply(Dart_Port port) {
|
|
PortMap::PostMessage(
|
|
Message::New(port, Object::null(), Message::kNormalPriority));
|
|
}
|
|
|
|
static void Finalizer(void* isolate_callback_data, void* buffer) {
|
|
free(buffer);
|
|
}
|
|
|
|
void JSONStream::PostReply() {
|
|
ASSERT(seq_ != NULL);
|
|
Dart_Port port = reply_port();
|
|
set_reply_port(ILLEGAL_PORT); // Prevent double replies.
|
|
if (seq_->IsString()) {
|
|
const String& str = String::Cast(*seq_);
|
|
PrintProperty("id", str.ToCString());
|
|
} else if (seq_->IsInteger()) {
|
|
const Integer& integer = Integer::Cast(*seq_);
|
|
PrintProperty64("id", integer.AsInt64Value());
|
|
} else if (seq_->IsDouble()) {
|
|
const Double& dbl = Double::Cast(*seq_);
|
|
PrintProperty("id", dbl.value());
|
|
} else if (seq_->IsNull()) {
|
|
if (port == ILLEGAL_PORT) {
|
|
// This path is only used in tests.
|
|
buffer()->AddChar('}'); // Finish our message.
|
|
char* cstr;
|
|
intptr_t length;
|
|
Steal(&cstr, &length);
|
|
OS::PrintErr("-----\nDropping reply:\n%s\n-----\n", cstr);
|
|
free(cstr);
|
|
}
|
|
// JSON-RPC 2.0 says that a request with a null ID shouldn't get a reply.
|
|
PostNullReply(port);
|
|
return;
|
|
}
|
|
ASSERT(port != ILLEGAL_PORT);
|
|
|
|
buffer()->AddChar('}'); // Finish our message.
|
|
char* cstr;
|
|
intptr_t length;
|
|
Steal(&cstr, &length);
|
|
|
|
bool result;
|
|
{
|
|
TransitionVMToNative transition(Thread::Current());
|
|
Dart_CObject bytes;
|
|
bytes.type = Dart_CObject_kExternalTypedData;
|
|
bytes.value.as_external_typed_data.type = Dart_TypedData_kUint8;
|
|
bytes.value.as_external_typed_data.length = length;
|
|
bytes.value.as_external_typed_data.data = reinterpret_cast<uint8_t*>(cstr);
|
|
bytes.value.as_external_typed_data.peer = cstr;
|
|
bytes.value.as_external_typed_data.callback = Finalizer;
|
|
Dart_CObject* elements[1];
|
|
elements[0] = &bytes;
|
|
Dart_CObject message;
|
|
message.type = Dart_CObject_kArray;
|
|
message.value.as_array.length = 1;
|
|
message.value.as_array.values = elements;
|
|
result = Dart_PostCObject(port, &message);
|
|
}
|
|
|
|
if (!result) {
|
|
free(cstr);
|
|
}
|
|
|
|
if (FLAG_trace_service) {
|
|
Isolate* isolate = Isolate::Current();
|
|
ASSERT(isolate != NULL);
|
|
int64_t main_port = static_cast<int64_t>(isolate->main_port());
|
|
const char* isolate_name = isolate->name();
|
|
int64_t total_time = OS::GetCurrentTimeMicros() - setup_time_micros_;
|
|
if (result) {
|
|
OS::PrintErr("[+%" Pd64 "ms] Isolate (%" Pd64
|
|
") %s processed service request %s (%" Pd64 "us)\n",
|
|
Dart::UptimeMillis(), main_port, isolate_name, method_,
|
|
total_time);
|
|
} else {
|
|
OS::PrintErr("[+%" Pd64 "ms] Isolate (%" Pd64
|
|
") %s processed service request %s (%" Pd64 "us) FAILED\n",
|
|
Dart::UptimeMillis(), main_port, isolate_name, method_,
|
|
total_time);
|
|
}
|
|
}
|
|
}
|
|
|
|
const char* JSONStream::LookupParam(const char* key) const {
|
|
for (int i = 0; i < num_params(); i++) {
|
|
if (strcmp(key, param_keys_[i]) == 0) {
|
|
return param_values_[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool JSONStream::HasParam(const char* key) const {
|
|
ASSERT(key);
|
|
return LookupParam(key) != NULL;
|
|
}
|
|
|
|
bool JSONStream::ParamIs(const char* key, const char* value) const {
|
|
ASSERT(key);
|
|
ASSERT(value);
|
|
const char* key_value = LookupParam(key);
|
|
return (key_value != NULL) && (strcmp(key_value, value) == 0);
|
|
}
|
|
|
|
void JSONStream::ComputeOffsetAndCount(intptr_t length,
|
|
intptr_t* offset,
|
|
intptr_t* count) {
|
|
// This function is written to avoid adding (count + offset) in case
|
|
// that triggers an integer overflow.
|
|
*offset = offset_;
|
|
if (*offset > length) {
|
|
*offset = length;
|
|
}
|
|
intptr_t remaining = length - *offset;
|
|
*count = count_;
|
|
if (*count < 0 || *count > remaining) {
|
|
*count = remaining;
|
|
}
|
|
}
|
|
void JSONStream::PrintfValue(const char* format, ...) {
|
|
va_list args;
|
|
va_start(args, format);
|
|
VPrintfValue(format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void JSONStream::PrintValue(const Object& o, bool ref) {
|
|
PrintCommaIfNeeded();
|
|
o.PrintJSON(this, ref);
|
|
}
|
|
|
|
void JSONStream::PrintValue(Breakpoint* bpt) {
|
|
PrintCommaIfNeeded();
|
|
bpt->PrintJSON(this);
|
|
}
|
|
|
|
void JSONStream::PrintValue(TokenPosition tp) {
|
|
PrintCommaIfNeeded();
|
|
PrintValue(static_cast<intptr_t>(tp.Serialize()));
|
|
}
|
|
|
|
void JSONStream::PrintValue(const ServiceEvent* event) {
|
|
PrintCommaIfNeeded();
|
|
event->PrintJSON(this);
|
|
}
|
|
|
|
void JSONStream::PrintValue(Metric* metric) {
|
|
PrintCommaIfNeeded();
|
|
metric->PrintJSON(this);
|
|
}
|
|
|
|
void JSONStream::PrintValue(MessageQueue* queue) {
|
|
PrintCommaIfNeeded();
|
|
queue->PrintJSON(this);
|
|
}
|
|
|
|
void JSONStream::PrintValue(Isolate* isolate, bool ref) {
|
|
PrintCommaIfNeeded();
|
|
isolate->PrintJSON(this, ref);
|
|
}
|
|
|
|
void JSONStream::PrintValue(IsolateGroup* isolate_group, bool ref) {
|
|
PrintCommaIfNeeded();
|
|
isolate_group->PrintJSON(this, ref);
|
|
}
|
|
|
|
void JSONStream::PrintValue(const TimelineEvent* timeline_event) {
|
|
PrintCommaIfNeeded();
|
|
timeline_event->PrintJSON(this);
|
|
}
|
|
|
|
void JSONStream::PrintValue(const TimelineEventBlock* timeline_event_block) {
|
|
PrintCommaIfNeeded();
|
|
timeline_event_block->PrintJSON(this);
|
|
}
|
|
|
|
void JSONStream::PrintValueVM(bool ref) {
|
|
PrintCommaIfNeeded();
|
|
Service::PrintJSONForVM(this, ref);
|
|
}
|
|
|
|
void JSONStream::PrintServiceId(const Object& o) {
|
|
ASSERT(id_zone_ != NULL);
|
|
PrintProperty("id", id_zone_->GetServiceId(o));
|
|
}
|
|
|
|
void JSONStream::PrintProperty(const char* name, const ServiceEvent* event) {
|
|
PrintPropertyName(name);
|
|
PrintValue(event);
|
|
}
|
|
|
|
void JSONStream::PrintProperty(const char* name, Breakpoint* bpt) {
|
|
PrintPropertyName(name);
|
|
PrintValue(bpt);
|
|
}
|
|
|
|
void JSONStream::PrintProperty(const char* name, TokenPosition tp) {
|
|
PrintPropertyName(name);
|
|
PrintValue(tp);
|
|
}
|
|
|
|
void JSONStream::PrintProperty(const char* name, Metric* metric) {
|
|
PrintPropertyName(name);
|
|
PrintValue(metric);
|
|
}
|
|
|
|
void JSONStream::PrintProperty(const char* name, MessageQueue* queue) {
|
|
PrintPropertyName(name);
|
|
PrintValue(queue);
|
|
}
|
|
|
|
void JSONStream::PrintProperty(const char* name, Isolate* isolate) {
|
|
PrintPropertyName(name);
|
|
PrintValue(isolate);
|
|
}
|
|
|
|
void JSONStream::PrintProperty(const char* name,
|
|
const TimelineEvent* timeline_event) {
|
|
PrintPropertyName(name);
|
|
PrintValue(timeline_event);
|
|
}
|
|
|
|
void JSONStream::PrintProperty(const char* name,
|
|
const TimelineEventBlock* timeline_event_block) {
|
|
PrintPropertyName(name);
|
|
PrintValue(timeline_event_block);
|
|
}
|
|
|
|
void JSONStream::PrintfProperty(const char* name, const char* format, ...) {
|
|
va_list args;
|
|
va_start(args, format);
|
|
writer_.VPrintfProperty(name, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void JSONStream::set_reply_port(Dart_Port port) {
|
|
reply_port_ = port;
|
|
}
|
|
|
|
intptr_t JSONStream::NumObjectParameters() const {
|
|
if (parameter_keys_ == NULL) {
|
|
return 0;
|
|
}
|
|
ASSERT(parameter_keys_ != NULL);
|
|
ASSERT(parameter_values_ != NULL);
|
|
return parameter_keys_->Length();
|
|
}
|
|
|
|
ObjectPtr JSONStream::GetObjectParameterKey(intptr_t i) const {
|
|
ASSERT((i >= 0) && (i < NumObjectParameters()));
|
|
return parameter_keys_->At(i);
|
|
}
|
|
|
|
ObjectPtr JSONStream::GetObjectParameterValue(intptr_t i) const {
|
|
ASSERT((i >= 0) && (i < NumObjectParameters()));
|
|
return parameter_values_->At(i);
|
|
}
|
|
|
|
ObjectPtr JSONStream::LookupObjectParam(const char* c_key) const {
|
|
const String& key = String::Handle(String::New(c_key));
|
|
Object& test = Object::Handle();
|
|
const intptr_t num_object_parameters = NumObjectParameters();
|
|
for (intptr_t i = 0; i < num_object_parameters; i++) {
|
|
test = GetObjectParameterKey(i);
|
|
if (test.IsString() && String::Cast(test).Equals(key)) {
|
|
return GetObjectParameterValue(i);
|
|
}
|
|
}
|
|
return Object::null();
|
|
}
|
|
|
|
void JSONStream::SetParams(const char** param_keys,
|
|
const char** param_values,
|
|
intptr_t num_params) {
|
|
param_keys_ = param_keys;
|
|
param_values_ = param_values;
|
|
num_params_ = num_params;
|
|
}
|
|
|
|
void JSONStream::PrintProperty(const char* name, const Object& o, bool ref) {
|
|
PrintPropertyName(name);
|
|
PrintValue(o, ref);
|
|
}
|
|
|
|
void JSONStream::PrintPropertyVM(const char* name, bool ref) {
|
|
PrintPropertyName(name);
|
|
PrintValueVM(ref);
|
|
}
|
|
|
|
JSONObject::JSONObject(const JSONArray* arr) : stream_(arr->stream_) {
|
|
stream_->OpenObject();
|
|
}
|
|
|
|
void JSONObject::AddFixedServiceId(const char* format, ...) const {
|
|
// Mark that this id is fixed.
|
|
AddProperty("fixedId", true);
|
|
// Add the id property.
|
|
va_list args;
|
|
va_start(args, format);
|
|
stream_->VPrintfProperty("id", format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void JSONObject::AddServiceId(const char* format, ...) const {
|
|
// Add the id property.
|
|
va_list args;
|
|
va_start(args, format);
|
|
stream_->VPrintfProperty("id", format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void JSONObject::AddLocation(const Script& script,
|
|
TokenPosition token_pos,
|
|
TokenPosition end_token_pos) const {
|
|
JSONObject location(this, "location");
|
|
location.AddProperty("type", "SourceLocation");
|
|
location.AddProperty("script", script);
|
|
location.AddProperty("tokenPos", token_pos);
|
|
if (end_token_pos.IsReal()) {
|
|
location.AddProperty("endTokenPos", end_token_pos);
|
|
}
|
|
}
|
|
|
|
void JSONObject::AddLocation(const BreakpointLocation* bpt_loc) const {
|
|
ASSERT(bpt_loc->IsResolved());
|
|
|
|
Zone* zone = Thread::Current()->zone();
|
|
Script& script = Script::Handle(zone);
|
|
TokenPosition token_pos = TokenPosition::kNoSource;
|
|
bpt_loc->GetCodeLocation(&script, &token_pos);
|
|
AddLocation(script, token_pos);
|
|
}
|
|
|
|
void JSONObject::AddUnresolvedLocation(
|
|
const BreakpointLocation* bpt_loc) const {
|
|
ASSERT(!bpt_loc->IsResolved());
|
|
|
|
Zone* zone = Thread::Current()->zone();
|
|
Script& script = Script::Handle(zone);
|
|
TokenPosition token_pos = TokenPosition::kNoSource;
|
|
bpt_loc->GetCodeLocation(&script, &token_pos);
|
|
|
|
JSONObject location(this, "location");
|
|
location.AddProperty("type", "UnresolvedSourceLocation");
|
|
if (!script.IsNull()) {
|
|
location.AddProperty("script", script);
|
|
} else {
|
|
const String& scriptUri = String::Handle(zone, bpt_loc->url());
|
|
location.AddPropertyStr("scriptUri", scriptUri);
|
|
}
|
|
if (bpt_loc->requested_line_number() >= 0) {
|
|
// This unresolved breakpoint was specified at a particular line.
|
|
location.AddProperty("line", bpt_loc->requested_line_number());
|
|
if (bpt_loc->requested_column_number() >= 0) {
|
|
location.AddProperty("column", bpt_loc->requested_column_number());
|
|
}
|
|
} else {
|
|
// This unresolved breakpoint was requested at some function entry.
|
|
location.AddProperty("tokenPos", token_pos);
|
|
}
|
|
}
|
|
|
|
void JSONObject::AddPropertyF(const char* name, const char* format, ...) const {
|
|
va_list args;
|
|
va_start(args, format);
|
|
stream_->VPrintfProperty(name, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void JSONArray::AddValueF(const char* format, ...) const {
|
|
va_list args;
|
|
va_start(args, format);
|
|
stream_->VPrintfValue(format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
#endif // !PRODUCT
|
|
|
|
} // namespace dart
|