Version 2.16.0-4.0.dev
Merge commit 'bbff4d95893fc8672df02bdbe3ea74f0fd0534f5' into 'dev'
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_suite.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_suite.dart
index bc5786d..7c5152d 100644
--- a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_suite.dart
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_suite.dart
@@ -126,7 +126,8 @@
var compilerOptions = SharedCompilerOptions(
replCompile: true,
moduleName: moduleName,
- soundNullSafety: setup.soundNullSafety);
+ soundNullSafety: setup.soundNullSafety,
+ emitDebugMetadata: true);
var coreTypes = compiler.getCoreTypes();
final importToSummary = Map<Library, Component>.identity();
@@ -145,10 +146,10 @@
var code = jsProgramToCode(
module,
setup.moduleFormat,
- inlineSourceMap: true,
- buildSourceMap: true,
- emitDebugMetadata: true,
- emitDebugSymbols: true,
+ inlineSourceMap: compilerOptions.inlineSourceMap,
+ buildSourceMap: compilerOptions.sourceMap,
+ emitDebugMetadata: compilerOptions.emitDebugMetadata,
+ emitDebugSymbols: compilerOptions.emitDebugSymbols,
jsUrl: '$output',
mapUrl: '$output.map',
compiler: kernel2jsCompiler,
diff --git a/runtime/include/dart_tools_api.h b/runtime/include/dart_tools_api.h
index f36ec6b..6d02750 100644
--- a/runtime/include/dart_tools_api.h
+++ b/runtime/include/dart_tools_api.h
@@ -363,6 +363,37 @@
*/
/**
+ * Enable tracking of specified timeline category. This is operational
+ * only when systrace timeline functionality is turned on.
+ *
+ * \param categories A comma seperated list of categories that need to
+ * be enabled, the categories are
+ * "all" : All categories
+ * "API" - Execution of Dart C API functions
+ * "Compiler" - Execution of Dart JIT compiler
+ * "CompilerVerbose" - More detailed Execution of Dart JIT compiler
+ * "Dart" - Execution of Dart code
+ * "Debugger" - Execution of Dart debugger
+ * "Embedder" - Execution of Dart embedder code
+ * "GC" - Execution of Dart Garbage Collector
+ * "Isolate" - Dart Isolate lifecycle execution
+ * "VM" - Excution in Dart VM runtime code
+ * "" - None
+ *
+ * When "all" is specified all the categories are enabled.
+ * When a comma seperated list of categories is specified, the categories
+ * that are specified will be enabled and the rest will be disabled.
+ * When "" is specified all the categories are disabled.
+ * The category names are case sensitive.
+ * eg: Dart_EnableTimelineCategory("all");
+ * Dart_EnableTimelineCategory("GC,API,Isolate");
+ * Dart_EnableTimelineCategory("GC,Debugger,Dart");
+ *
+ * \return True if the categories were successfully enabled, False otherwise.
+ */
+DART_EXPORT bool Dart_SetEnabledTimelineCategory(const char* categories);
+
+/**
* Returns a timestamp in microseconds. This timestamp is suitable for
* passing into the timeline system, and uses the same monotonic clock
* as dart:developer's Timeline.now.
diff --git a/runtime/vm/compiler/backend/il_arm64.cc b/runtime/vm/compiler/backend/il_arm64.cc
index e13d55f..85e22b5 100644
--- a/runtime/vm/compiler/backend/il_arm64.cc
+++ b/runtime/vm/compiler/backend/il_arm64.cc
@@ -2300,7 +2300,7 @@
compiler::FieldAddress(
field_reg, Field::guarded_list_length_in_object_offset_offset()),
compiler::kByte);
- __ LoadCompressed(
+ __ LoadCompressedSmi(
length_reg,
compiler::FieldAddress(field_reg, Field::guarded_list_length_offset()));
@@ -2311,7 +2311,7 @@
// value's class matches guarded class id of the field.
// offset_reg contains offset already corrected by -kHeapObjectTag that is
// why we use Address instead of FieldAddress.
- __ ldr(TMP, compiler::Address(value_reg, offset_reg));
+ __ LoadCompressedSmi(TMP, compiler::Address(value_reg, offset_reg));
__ CompareObjectRegisters(length_reg, TMP);
if (deopt == NULL) {
diff --git a/runtime/vm/compiler/backend/il_x64.cc b/runtime/vm/compiler/backend/il_x64.cc
index 97f1552..4ca35a6 100644
--- a/runtime/vm/compiler/backend/il_x64.cc
+++ b/runtime/vm/compiler/backend/il_x64.cc
@@ -2288,7 +2288,7 @@
offset_reg,
compiler::FieldAddress(
field_reg, Field::guarded_list_length_in_object_offset_offset()));
- __ LoadCompressed(
+ __ LoadCompressedSmi(
length_reg,
compiler::FieldAddress(field_reg, Field::guarded_list_length_offset()));
diff --git a/runtime/vm/compiler/frontend/base_flow_graph_builder.cc b/runtime/vm/compiler/frontend/base_flow_graph_builder.cc
index 7b9bac9..2f8ac3b 100644
--- a/runtime/vm/compiler/frontend/base_flow_graph_builder.cc
+++ b/runtime/vm/compiler/frontend/base_flow_graph_builder.cc
@@ -19,6 +19,14 @@
#define Z (zone_)
#define IG (thread_->isolate_group())
+static bool SupportsCoverage() {
+#if defined(PRODUCT)
+ return false;
+#else
+ return !CompilerState::Current().is_aot();
+#endif
+}
+
Fragment& Fragment::operator+=(const Fragment& other) {
if (entry == NULL) {
entry = other.entry;
@@ -1138,25 +1146,11 @@
return call_hook;
}
-static bool SupportsCoverage() {
-#if defined(PRODUCT)
- return false;
-#else
- return !CompilerState::Current().is_aot();
-#endif
-}
-
Fragment BaseFlowGraphBuilder::ClosureCall(TokenPosition position,
intptr_t type_args_len,
intptr_t argument_count,
const Array& argument_names) {
- Fragment result;
-
- if (SupportsCoverage()) {
- const intptr_t coverage_index = GetCoverageIndexFor(position);
- result <<= new (Z) RecordCoverageInstr(coverage_array(), coverage_index,
- InstructionSource(position));
- }
+ Fragment result = RecordCoverage(position);
const intptr_t total_count =
(type_args_len > 0 ? 1 : 0) + argument_count +
/*closure (bare instructions) or function (otherwise)*/ 1;
@@ -1249,6 +1243,16 @@
return Fragment(instr);
}
+Fragment BaseFlowGraphBuilder::RecordCoverage(TokenPosition position) {
+ Fragment instructions;
+ if (SupportsCoverage()) {
+ const intptr_t coverage_index = GetCoverageIndexFor(position);
+ instructions <<= new (Z) RecordCoverageInstr(
+ coverage_array(), coverage_index, InstructionSource(position));
+ }
+ return instructions;
+}
+
intptr_t BaseFlowGraphBuilder::GetCoverageIndexFor(TokenPosition token_pos) {
if (coverage_array_.IsNull()) {
// We have not yet created coverage_array, this is the first time
diff --git a/runtime/vm/compiler/frontend/base_flow_graph_builder.h b/runtime/vm/compiler/frontend/base_flow_graph_builder.h
index ee7e7ce..b0597b8 100644
--- a/runtime/vm/compiler/frontend/base_flow_graph_builder.h
+++ b/runtime/vm/compiler/frontend/base_flow_graph_builder.h
@@ -471,6 +471,9 @@
// Pops double value and applies unary math operation.
Fragment MathUnary(MathUnaryInstr::MathUnaryKind kind);
+ // Records coverage for this position, if the current VM mode supports it.
+ Fragment RecordCoverage(TokenPosition position);
+
// Returns whether this function has a saved arguments descriptor array.
bool has_saved_args_desc_array() {
return function_.HasSavedArgumentsDescriptor();
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
index 6bf2d87..08e04f9 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
@@ -1870,6 +1870,10 @@
type, variable->name(), AssertAssignableInstr::kParameterCheck);
}
+Fragment StreamingFlowGraphBuilder::RecordCoverage(TokenPosition position) {
+ return flow_graph_builder_->RecordCoverage(position);
+}
+
Fragment StreamingFlowGraphBuilder::EnterScope(
intptr_t kernel_offset,
const LocalScope** scope /* = nullptr */) {
@@ -4338,6 +4342,7 @@
ReadPosition(); // read condition end offset.
instructions += EvaluateAssertion();
+ instructions += RecordCoverage(condition_start_offset);
instructions += CheckBoolean(condition_start_offset);
instructions += Constant(Bool::True());
instructions += BranchIfEqual(&then, &otherwise, false);
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
index 3cd6257..0f40918 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
@@ -254,6 +254,7 @@
Fragment BuildImplicitClosureCreation(const Function& target);
Fragment CheckBoolean(TokenPosition position);
Fragment CheckArgumentType(LocalVariable* variable, const AbstractType& type);
+ Fragment RecordCoverage(TokenPosition position);
Fragment EnterScope(intptr_t kernel_offset,
const LocalScope** scope = nullptr);
Fragment ExitScope(intptr_t kernel_offset);
diff --git a/runtime/vm/compiler/stub_code_compiler_arm64.cc b/runtime/vm/compiler/stub_code_compiler_arm64.cc
index 54196b7..1ea25d2 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm64.cc
@@ -150,8 +150,7 @@
__ StoreToOffset(R2, THR, target::Thread::vm_tag_offset());
// Mark that the thread has not exited generated Dart code.
- __ LoadImmediate(R2, 0);
- __ StoreToOffset(R2, THR, target::Thread::exit_through_ffi_offset());
+ __ StoreToOffset(ZR, THR, target::Thread::exit_through_ffi_offset());
// Reset exit frame information in Isolate's mutator thread structure.
__ StoreToOffset(ZR, THR, target::Thread::top_exit_frame_info_offset());
@@ -722,8 +721,7 @@
__ StoreToOffset(R2, THR, target::Thread::vm_tag_offset());
// Mark that the thread has not exited generated Dart code.
- __ LoadImmediate(R2, 0);
- __ StoreToOffset(R2, THR, target::Thread::exit_through_ffi_offset());
+ __ StoreToOffset(ZR, THR, target::Thread::exit_through_ffi_offset());
// Reset exit frame information in Isolate's mutator thread structure.
__ StoreToOffset(ZR, THR, target::Thread::top_exit_frame_info_offset());
@@ -1389,8 +1387,7 @@
__ LoadFromOffset(R6, THR, target::Thread::exit_through_ffi_offset());
__ Push(R6);
- __ LoadImmediate(R6, 0);
- __ StoreToOffset(R6, THR, target::Thread::exit_through_ffi_offset());
+ __ StoreToOffset(ZR, THR, target::Thread::exit_through_ffi_offset());
__ LoadFromOffset(R6, THR, target::Thread::top_exit_frame_info_offset());
__ StoreToOffset(ZR, THR, target::Thread::top_exit_frame_info_offset());
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 88dc441..437ce6f 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -6272,6 +6272,20 @@
#endif
}
+DART_EXPORT bool Dart_SetEnabledTimelineCategory(const char* categories) {
+#if defined(SUPPORT_TIMELINE)
+ bool result = false;
+ if (categories != nullptr) {
+ char* carray = Utils::SCreate("[%s]", categories);
+ result = Service::EnableTimelineStreams(carray);
+ free(carray);
+ }
+ return result;
+#else
+ return false;
+#endif
+}
+
DART_EXPORT int64_t Dart_TimelineGetMicros() {
return OS::GetCurrentMonotonicMicros();
}
diff --git a/runtime/vm/dart_api_impl_test.cc b/runtime/vm/dart_api_impl_test.cc
index 7074ded..5a9050a 100644
--- a/runtime/vm/dart_api_impl_test.cc
+++ b/runtime/vm/dart_api_impl_test.cc
@@ -9323,6 +9323,84 @@
EXPECT_EQ(frequency1, frequency2);
}
+TEST_CASE(DartAPI_TimelineCategories) {
+ bool result;
+ {
+ result = Dart_SetEnabledTimelineCategory("all");
+ EXPECT_EQ(true, result);
+ JSONStream js;
+ JSONObject obj(&js);
+ JSONArray jstream(&obj, "available");
+ Timeline::PrintFlagsToJSONArray(&jstream);
+ const char* js_str = js.ToCString();
+#define TIMELINE_STREAM_CHECK(name, fuchsia_name) \
+ EXPECT_SUBSTRING(#name, js_str);
+ TIMELINE_STREAM_LIST(TIMELINE_STREAM_CHECK)
+#undef TIMELINE_STREAM_CHECK
+ }
+
+ {
+ result = Dart_SetEnabledTimelineCategory(nullptr);
+ EXPECT_EQ(false, result);
+ result = Dart_SetEnabledTimelineCategory("GC,api,compiler");
+ EXPECT_EQ(false, result);
+ }
+
+ {
+ result = Dart_SetEnabledTimelineCategory("GC,API,Compiler");
+ EXPECT_EQ(true, result);
+ JSONStream js;
+ JSONObject obj(&js);
+ JSONArray jstream(&obj, "available");
+ Timeline::PrintFlagsToJSONArray(&jstream);
+ const char* js_str = js.ToCString();
+ EXPECT_SUBSTRING("GC", js_str);
+ EXPECT_SUBSTRING("API", js_str);
+ EXPECT_SUBSTRING("Compiler", js_str);
+ EXPECT_NOTSUBSTRING("CompilerVerbose", js_str);
+ EXPECT_NOTSUBSTRING("Debugger", js_str);
+ EXPECT_NOTSUBSTRING("Embedder", js_str);
+ EXPECT_NOTSUBSTRING("Isolate", js_str);
+ EXPECT_NOTSUBSTRING("VM", js_str);
+ }
+
+ {
+ result = Dart_SetEnabledTimelineCategory("Isolate");
+ EXPECT_EQ(true, result);
+ JSONStream js;
+ JSONObject obj(&js);
+ JSONArray jstream(&obj, "available");
+ Timeline::PrintFlagsToJSONArray(&jstream);
+ const char* js_str = js.ToCString();
+ EXPECT_NOTSUBSTRING("GC", js_str);
+ EXPECT_NOTSUBSTRING("API", js_str);
+ EXPECT_NOTSUBSTRING("Compiler", js_str);
+ EXPECT_NOTSUBSTRING("CompilerVerbose", js_str);
+ EXPECT_NOTSUBSTRING("Debugger", js_str);
+ EXPECT_NOTSUBSTRING("Embedder", js_str);
+ EXPECT_SUBSTRING("Isolate", js_str);
+ EXPECT_NOTSUBSTRING("VM", js_str);
+ }
+
+ {
+ result = Dart_SetEnabledTimelineCategory("");
+ EXPECT_EQ(true, result);
+ JSONStream js;
+ JSONObject obj(&js);
+ JSONArray jstream(&obj, "available");
+ Timeline::PrintFlagsToJSONArray(&jstream);
+ const char* js_str = js.ToCString();
+ EXPECT_NOTSUBSTRING("GC", js_str);
+ EXPECT_NOTSUBSTRING("API", js_str);
+ EXPECT_NOTSUBSTRING("Compiler", js_str);
+ EXPECT_NOTSUBSTRING("CompilerVerbose", js_str);
+ EXPECT_NOTSUBSTRING("Debugger", js_str);
+ EXPECT_NOTSUBSTRING("Embedder", js_str);
+ EXPECT_NOTSUBSTRING("Isolate", js_str);
+ EXPECT_NOTSUBSTRING("VM", js_str);
+ }
+}
+
static void HintFreedNative(Dart_NativeArguments args) {
int64_t size = 0;
EXPECT_VALID(Dart_GetNativeIntegerArgument(args, 0, &size));
diff --git a/runtime/vm/heap/compactor.cc b/runtime/vm/heap/compactor.cc
index 7311ecf..2051b0c 100644
--- a/runtime/vm/heap/compactor.cc
+++ b/runtime/vm/heap/compactor.cc
@@ -117,21 +117,30 @@
forwarding_page_ = reinterpret_cast<ForwardingPage*>(object_end_);
}
+struct Partition {
+ OldPage* head;
+ OldPage* tail;
+};
+
class CompactorTask : public ThreadPool::Task {
public:
CompactorTask(IsolateGroup* isolate_group,
GCCompactor* compactor,
ThreadBarrier* barrier,
+ RelaxedAtomic<intptr_t>* next_planning_task,
+ RelaxedAtomic<intptr_t>* next_sliding_task,
RelaxedAtomic<intptr_t>* next_forwarding_task,
- OldPage* head,
- OldPage** tail,
+ intptr_t num_tasks,
+ Partition* partitions,
FreeList* freelist)
: isolate_group_(isolate_group),
compactor_(compactor),
barrier_(barrier),
+ next_planning_task_(next_planning_task),
+ next_sliding_task_(next_sliding_task),
next_forwarding_task_(next_forwarding_task),
- head_(head),
- tail_(tail),
+ num_tasks_(num_tasks),
+ partitions_(partitions),
freelist_(freelist),
free_page_(NULL),
free_current_(0),
@@ -150,9 +159,11 @@
IsolateGroup* isolate_group_;
GCCompactor* compactor_;
ThreadBarrier* barrier_;
+ RelaxedAtomic<intptr_t>* next_planning_task_;
+ RelaxedAtomic<intptr_t>* next_sliding_task_;
RelaxedAtomic<intptr_t>* next_forwarding_task_;
- OldPage* head_;
- OldPage** tail_;
+ intptr_t num_tasks_;
+ Partition* partitions_;
FreeList* freelist_;
OldPage* free_page_;
uword free_current_;
@@ -184,8 +195,8 @@
if (num_pages < num_tasks) {
num_tasks = num_pages;
}
- OldPage** heads = new OldPage*[num_tasks];
- OldPage** tails = new OldPage*[num_tasks];
+
+ Partition* partitions = new Partition[num_tasks];
{
const intptr_t pages_per_task = num_pages / num_tasks;
@@ -195,8 +206,8 @@
OldPage* prev = NULL;
while (task_index < num_tasks) {
if (page_index % pages_per_task == 0) {
- heads[task_index] = page;
- tails[task_index] = NULL;
+ partitions[task_index].head = page;
+ partitions[task_index].tail = NULL;
if (prev != NULL) {
prev->set_next(NULL);
}
@@ -232,27 +243,31 @@
page->object_end() - page->object_start());
// The compactor slides down: add the empty pages to the beginning.
- page->set_next(heads[task_index]);
- heads[task_index] = page;
+ page->set_next(partitions[task_index].head);
+ partitions[task_index].head = page;
}
}
}
{
- ThreadBarrier* barrier = new ThreadBarrier(num_tasks, num_tasks);
+ ThreadBarrier* barrier = new ThreadBarrier(num_tasks, 1);
+ RelaxedAtomic<intptr_t> next_planning_task = {0};
+ RelaxedAtomic<intptr_t> next_sliding_task = {0};
RelaxedAtomic<intptr_t> next_forwarding_task = {0};
for (intptr_t task_index = 0; task_index < num_tasks; task_index++) {
if (task_index < (num_tasks - 1)) {
// Begin compacting on a helper thread.
Dart::thread_pool()->Run<CompactorTask>(
- thread()->isolate_group(), this, barrier, &next_forwarding_task,
- heads[task_index], &tails[task_index], freelist);
+ thread()->isolate_group(), this, barrier, &next_planning_task,
+ &next_sliding_task, &next_forwarding_task, num_tasks, partitions,
+ freelist);
} else {
// Last worker is the main thread.
CompactorTask task(thread()->isolate_group(), this, barrier,
- &next_forwarding_task, heads[task_index],
- &tails[task_index], freelist);
+ &next_planning_task, &next_sliding_task,
+ &next_forwarding_task, num_tasks, partitions,
+ freelist);
task.RunEnteredIsolateGroup();
barrier->Sync();
barrier->Release();
@@ -290,7 +305,7 @@
}
for (intptr_t task_index = 0; task_index < num_tasks; task_index++) {
- ASSERT(tails[task_index] != NULL);
+ ASSERT(partitions[task_index].tail != NULL);
}
{
@@ -305,7 +320,7 @@
// Free empty pages.
for (intptr_t task_index = 0; task_index < num_tasks; task_index++) {
- OldPage* page = tails[task_index]->next();
+ OldPage* page = partitions[task_index].tail->next();
while (page != NULL) {
OldPage* next = page->next();
heap_->old_space()->IncreaseCapacityInWordsLocked(
@@ -317,18 +332,22 @@
// Re-join the heap.
for (intptr_t task_index = 0; task_index < num_tasks - 1; task_index++) {
- tails[task_index]->set_next(heads[task_index + 1]);
+ partitions[task_index].tail->set_next(partitions[task_index + 1].head);
}
- tails[num_tasks - 1]->set_next(NULL);
- heap_->old_space()->pages_ = pages = heads[0];
- heap_->old_space()->pages_tail_ = tails[num_tasks - 1];
+ partitions[num_tasks - 1].tail->set_next(NULL);
+ heap_->old_space()->pages_ = pages = partitions[0].head;
+ heap_->old_space()->pages_tail_ = partitions[num_tasks - 1].tail;
- delete[] heads;
- delete[] tails;
+ delete[] partitions;
}
}
void CompactorTask::Run() {
+ if (!barrier_->TryEnter()) {
+ barrier_->Release();
+ return;
+ }
+
bool result =
Thread::EnterIsolateGroupAsHelper(isolate_group_, Thread::kCompactorTask,
/*bypass_safepoint=*/true);
@@ -348,26 +367,34 @@
Thread* thread = Thread::Current();
#endif
{
- {
- TIMELINE_FUNCTION_GC_DURATION(thread, "Plan");
- free_page_ = head_;
- free_current_ = free_page_->object_start();
- free_end_ = free_page_->object_end();
+ while (true) {
+ intptr_t planning_task = next_planning_task_->fetch_add(1u);
+ if (planning_task >= num_tasks_) break;
- for (OldPage* page = head_; page != NULL; page = page->next()) {
+ TIMELINE_FUNCTION_GC_DURATION(thread, "Plan");
+ OldPage* head = partitions_[planning_task].head;
+ free_page_ = head;
+ free_current_ = head->object_start();
+ free_end_ = head->object_end();
+
+ for (OldPage* page = head; page != NULL; page = page->next()) {
PlanPage(page);
}
}
barrier_->Sync();
- {
- TIMELINE_FUNCTION_GC_DURATION(thread, "Slide");
- free_page_ = head_;
- free_current_ = free_page_->object_start();
- free_end_ = free_page_->object_end();
+ while (true) {
+ intptr_t sliding_task = next_sliding_task_->fetch_add(1u);
+ if (sliding_task >= num_tasks_) break;
- for (OldPage* page = head_; page != NULL; page = page->next()) {
+ TIMELINE_FUNCTION_GC_DURATION(thread, "Slide");
+ OldPage* head = partitions_[sliding_task].head;
+ free_page_ = head;
+ free_current_ = head->object_start();
+ free_end_ = head->object_end();
+
+ for (OldPage* page = head; page != NULL; page = page->next()) {
SlidePage(page);
}
@@ -379,7 +406,7 @@
}
ASSERT(free_page_ != NULL);
- *tail_ = free_page_; // Last live page.
+ partitions_[sliding_task].tail = free_page_; // Last live page.
}
// Heap: Regular pages already visited during sliding. Code and image pages
diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h
index b1e4939..f9616f3 100644
--- a/runtime/vm/isolate.h
+++ b/runtime/vm/isolate.h
@@ -482,6 +482,11 @@
ShouldLoadVmServiceBit::update(value, isolate_group_flags_);
}
+ void set_asserts(bool value) {
+ isolate_group_flags_ =
+ EnableAssertsBit::update(value, isolate_group_flags_);
+ }
+
#if !defined(PRODUCT)
#if !defined(DART_PRECOMPILED_RUNTIME)
bool HasAttemptedReload() const {
diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc
index a75737e..b70dea0 100644
--- a/runtime/vm/service.cc
+++ b/runtime/vm/service.cc
@@ -72,6 +72,231 @@
"Print a message when an isolate is paused but there is no "
"debugger attached.");
+static void PrintInvalidParamError(JSONStream* js, const char* param) {
+#if !defined(PRODUCT)
+ js->PrintError(kInvalidParams, "%s: invalid '%s' parameter: %s", js->method(),
+ param, js->LookupParam(param));
+#endif
+}
+
+// TODO(johnmccutchan): Split into separate file and write unit tests.
+class MethodParameter {
+ public:
+ MethodParameter(const char* name, bool required)
+ : name_(name), required_(required) {}
+
+ virtual ~MethodParameter() {}
+
+ virtual bool Validate(const char* value) const { return true; }
+
+ virtual bool ValidateObject(const Object& value) const { return true; }
+
+ const char* name() const { return name_; }
+
+ bool required() const { return required_; }
+
+ virtual void PrintError(const char* name,
+ const char* value,
+ JSONStream* js) const {
+ PrintInvalidParamError(js, name);
+ }
+
+ virtual void PrintErrorObject(const char* name,
+ const Object& value,
+ JSONStream* js) const {
+ PrintInvalidParamError(js, name);
+ }
+
+ private:
+ const char* name_;
+ bool required_;
+};
+
+class NoSuchParameter : public MethodParameter {
+ public:
+ explicit NoSuchParameter(const char* name) : MethodParameter(name, false) {}
+
+ virtual bool Validate(const char* value) const { return (value == NULL); }
+
+ virtual bool ValidateObject(const Object& value) const {
+ return value.IsNull();
+ }
+};
+
+#define ISOLATE_PARAMETER new IdParameter("isolateId", true)
+#define ISOLATE_GROUP_PARAMETER new IdParameter("isolateGroupId", true)
+#define NO_ISOLATE_PARAMETER new NoSuchParameter("isolateId")
+#define RUNNABLE_ISOLATE_PARAMETER new RunnableIsolateParameter("isolateId")
+
+class EnumListParameter : public MethodParameter {
+ public:
+ EnumListParameter(const char* name, bool required, const char* const* enums)
+ : MethodParameter(name, required), enums_(enums) {}
+
+ virtual bool Validate(const char* value) const {
+ return ElementCount(value) >= 0;
+ }
+
+ const char** Parse(char* value) const {
+ const char* kJsonChars = " \t\r\n[,]";
+
+ // Make a writeable copy of the value.
+ intptr_t element_count = ElementCount(value);
+ if (element_count < 0) {
+ return nullptr;
+ }
+ intptr_t element_pos = 0;
+
+ // Allocate our element array. +1 for NULL terminator.
+ // The caller is reponsible for deleting this memory.
+ char** elements = new 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;
+ const char* id_start = cp;
+ while (IsEnumChar(*cp)) {
+ cp++;
+ }
+ if (cp == id_start) {
+ // Empty identifier, something like this [,].
+ return -1;
+ }
+ intptr_t id_len = cp - id_start;
+ if (enums_ != NULL) {
+ for (intptr_t i = 0; enums_[i] != NULL; i++) {
+ intptr_t len = strlen(enums_[i]);
+ if (len == id_len && strncmp(id_start, enums_[i], len) == 0) {
+ element_count++;
+ valid_enum = true;
+ element_allowed = false; // we need a comma first.
+ break;
+ }
+ }
+ }
+ if (!valid_enum) {
+ return -1;
+ }
+ break;
+ }
+ }
+ }
+
+ const char* const* enums_;
+};
+
+#if defined(SUPPORT_TIMELINE)
+static const char* const timeline_streams_enum_names[] = {
+ "all",
+#define DEFINE_NAME(name, unused) #name,
+ TIMELINE_STREAM_LIST(DEFINE_NAME)
+#undef DEFINE_NAME
+ NULL};
+
+static const MethodParameter* const set_vm_timeline_flags_params[] = {
+ NO_ISOLATE_PARAMETER,
+ new EnumListParameter("recordedStreams",
+ false,
+ timeline_streams_enum_names),
+ NULL,
+};
+
+static bool HasStream(const char** recorded_streams, const char* stream) {
+ while (*recorded_streams != NULL) {
+ if ((strstr(*recorded_streams, "all") != NULL) ||
+ (strstr(*recorded_streams, stream) != NULL)) {
+ return true;
+ }
+ recorded_streams++;
+ }
+ return false;
+}
+
+bool Service::EnableTimelineStreams(char* categories_list) {
+ const EnumListParameter* recorded_streams_param =
+ static_cast<const EnumListParameter*>(set_vm_timeline_flags_params[1]);
+ const char** streams = recorded_streams_param->Parse(categories_list);
+ if (streams == nullptr) {
+ return false;
+ }
+
+#define SET_ENABLE_STREAM(name, unused) \
+ Timeline::SetStream##name##Enabled(HasStream(streams, #name));
+ TIMELINE_STREAM_LIST(SET_ENABLE_STREAM);
+#undef SET_ENABLE_STREAM
+
+ delete[] streams;
+
+#if !defined(PRODUCT)
+ // Notify clients that the set of subscribed streams has been updated.
+ if (Service::timeline_stream.enabled()) {
+ ServiceEvent event(ServiceEvent::kTimelineStreamSubscriptionsUpdate);
+ Service::HandleEvent(&event);
+ }
+#endif
+
+ return true;
+}
+#endif // defined(SUPPORT_TIMELINE)
+
#ifndef PRODUCT
// The name of this of this vm as reported by the VM service protocol.
static char* vm_name = NULL;
@@ -220,20 +445,6 @@
return object.ptr();
}
-static void PrintMissingParamError(JSONStream* js, const char* param) {
- js->PrintError(kInvalidParams, "%s expects the '%s' parameter", js->method(),
- param);
-}
-
-static void PrintInvalidParamError(JSONStream* js, const char* param) {
- js->PrintError(kInvalidParams, "%s: invalid '%s' parameter: %s", js->method(),
- param, js->LookupParam(param));
-}
-
-static void PrintUnrecognizedMethodError(JSONStream* js) {
- js->PrintError(kMethodNotFound, NULL);
-}
-
static void PrintSuccess(JSONStream* js) {
JSONObject jsobj(js);
jsobj.AddProperty("type", "Success");
@@ -430,39 +641,6 @@
return class_table->At(cid);
}
-// TODO(johnmccutchan): Split into separate file and write unit tests.
-class MethodParameter {
- public:
- MethodParameter(const char* name, bool required)
- : name_(name), required_(required) {}
-
- virtual ~MethodParameter() {}
-
- virtual bool Validate(const char* value) const { return true; }
-
- virtual bool ValidateObject(const Object& value) const { return true; }
-
- const char* name() const { return name_; }
-
- bool required() const { return required_; }
-
- virtual void PrintError(const char* name,
- const char* value,
- JSONStream* js) const {
- PrintInvalidParamError(js, name);
- }
-
- virtual void PrintErrorObject(const char* name,
- const Object& value,
- JSONStream* js) const {
- PrintInvalidParamError(js, name);
- }
-
- private:
- const char* name_;
- bool required_;
-};
-
class DartStringParameter : public MethodParameter {
public:
DartStringParameter(const char* name, bool required)
@@ -483,17 +661,6 @@
}
};
-class NoSuchParameter : public MethodParameter {
- public:
- explicit NoSuchParameter(const char* name) : MethodParameter(name, false) {}
-
- virtual bool Validate(const char* value) const { return (value == NULL); }
-
- virtual bool ValidateObject(const Object& value) const {
- return value.IsNull();
- }
-};
-
class BoolParameter : public MethodParameter {
public:
BoolParameter(const char* name, bool required)
@@ -632,11 +799,6 @@
}
};
-#define ISOLATE_PARAMETER new IdParameter("isolateId", true)
-#define ISOLATE_GROUP_PARAMETER new IdParameter("isolateGroupId", true)
-#define NO_ISOLATE_PARAMETER new NoSuchParameter("isolateId")
-#define RUNNABLE_ISOLATE_PARAMETER new RunnableIsolateParameter("isolateId")
-
class EnumParameter : public MethodParameter {
public:
EnumParameter(const char* name, bool required, const char* const* enums)
@@ -673,118 +835,6 @@
return values[i];
}
-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;
- const char* id_start = cp;
- while (IsEnumChar(*cp)) {
- cp++;
- }
- if (cp == id_start) {
- // Empty identifier, something like this [,].
- return -1;
- }
- intptr_t id_len = cp - id_start;
- if (enums_ != NULL) {
- for (intptr_t i = 0; enums_[i] != NULL; i++) {
- intptr_t len = strlen(enums_[i]);
- if (len == id_len && strncmp(id_start, enums_[i], len) == 0) {
- element_count++;
- valid_enum = true;
- element_allowed = false; // we need a comma first.
- break;
- }
- }
- }
- if (!valid_enum) {
- return -1;
- }
- break;
- }
- }
- }
-
- const char* const* enums_;
-};
-
typedef void (*ServiceMethodEntry)(Thread* thread, JSONStream* js);
struct ServiceMethodDescriptor {
@@ -793,6 +843,15 @@
const MethodParameter* const* parameters;
};
+static void PrintMissingParamError(JSONStream* js, const char* param) {
+ js->PrintError(kInvalidParams, "%s expects the '%s' parameter", js->method(),
+ param);
+}
+
+static void PrintUnrecognizedMethodError(JSONStream* js) {
+ js->PrintError(kMethodNotFound, NULL);
+}
+
// TODO(johnmccutchan): Do we reject unexpected parameters?
static bool ValidateParameters(const MethodParameter* const* parameters,
JSONStream* js) {
@@ -3349,23 +3408,28 @@
return;
}
- const char* reports_str = js->LookupParam("reports");
+ char* reports_str = Utils::StrDup(js->LookupParam("reports"));
const EnumListParameter* reports_parameter =
static_cast<const EnumListParameter*>(get_source_report_params[1]);
- const char** reports = reports_parameter->Parse(thread->zone(), reports_str);
+ const char** reports = reports_parameter->Parse(reports_str);
+ const char** riter = reports;
intptr_t report_set = 0;
- while (*reports != NULL) {
- if (strcmp(*reports, SourceReport::kCallSitesStr) == 0) {
+ while (*riter != NULL) {
+ if (strcmp(*riter, SourceReport::kCallSitesStr) == 0) {
report_set |= SourceReport::kCallSites;
- } else if (strcmp(*reports, SourceReport::kCoverageStr) == 0) {
+ } else if (strcmp(*riter, SourceReport::kCoverageStr) == 0) {
report_set |= SourceReport::kCoverage;
- } else if (strcmp(*reports, SourceReport::kPossibleBreakpointsStr) == 0) {
+ } else if (strcmp(*riter, SourceReport::kPossibleBreakpointsStr) == 0) {
report_set |= SourceReport::kPossibleBreakpoints;
- } else if (strcmp(*reports, SourceReport::kProfileStr) == 0) {
+ } else if (strcmp(*riter, SourceReport::kProfileStr) == 0) {
report_set |= SourceReport::kProfile;
}
- reports++;
+ riter++;
}
+ if (reports != nullptr) {
+ delete[] reports;
+ }
+ free(reports_str);
SourceReport::CompileMode compile_mode = SourceReport::kNoCompile;
if (BoolParameter::Parse(js->LookupParam("forceCompile"), false)) {
@@ -3803,34 +3867,6 @@
}
}
-static const char* const timeline_streams_enum_names[] = {
- "all",
-#define DEFINE_NAME(name, unused) #name,
- TIMELINE_STREAM_LIST(DEFINE_NAME)
-#undef DEFINE_NAME
- NULL};
-
-static const MethodParameter* const set_vm_timeline_flags_params[] = {
- NO_ISOLATE_PARAMETER,
- new EnumListParameter("recordedStreams",
- false,
- timeline_streams_enum_names),
- NULL,
-};
-
-#if defined(SUPPORT_TIMELINE)
-static bool HasStream(const char** recorded_streams, const char* stream) {
- while (*recorded_streams != NULL) {
- if ((strstr(*recorded_streams, "all") != NULL) ||
- (strstr(*recorded_streams, stream) != NULL)) {
- return true;
- }
- recorded_streams++;
- }
- return false;
-}
-#endif
-
static void SetVMTimelineFlags(Thread* thread, JSONStream* js) {
#if !defined(SUPPORT_TIMELINE)
PrintSuccess(js);
@@ -3839,23 +3875,9 @@
ASSERT(isolate != NULL);
StackZone zone(thread);
- const EnumListParameter* recorded_streams_param =
- static_cast<const EnumListParameter*>(set_vm_timeline_flags_params[1]);
-
- const char* recorded_streams_str = js->LookupParam("recordedStreams");
- const char** recorded_streams =
- recorded_streams_param->Parse(thread->zone(), recorded_streams_str);
-
-#define SET_ENABLE_STREAM(name, unused) \
- Timeline::SetStream##name##Enabled(HasStream(recorded_streams, #name));
- TIMELINE_STREAM_LIST(SET_ENABLE_STREAM);
-#undef SET_ENABLE_STREAM
-
- // Notify clients that the set of subscribed streams has been updated.
- if (Service::timeline_stream.enabled()) {
- ServiceEvent event(ServiceEvent::kTimelineStreamSubscriptionsUpdate);
- Service::HandleEvent(&event);
- }
+ char* recorded_streams = Utils::StrDup(js->LookupParam("recordedStreams"));
+ Service::EnableTimelineStreams(recorded_streams);
+ free(recorded_streams);
PrintSuccess(js);
#endif
diff --git a/runtime/vm/service.h b/runtime/vm/service.h
index c3fe1f2..f51eb10 100644
--- a/runtime/vm/service.h
+++ b/runtime/vm/service.h
@@ -161,6 +161,10 @@
const Instance& id,
const Error& error);
+ // Enable/Disable timeline categories.
+ // Returns True if the categories were successfully enabled, False otherwise.
+ static bool EnableTimelineStreams(char* categories_list);
+
// Well-known streams.
static StreamInfo vm_stream;
static StreamInfo isolate_stream;
diff --git a/runtime/vm/source_report_test.cc b/runtime/vm/source_report_test.cc
index 00c4c2e..fe21bbe 100644
--- a/runtime/vm/source_report_test.cc
+++ b/runtime/vm/source_report_test.cc
@@ -819,6 +819,51 @@
buffer);
}
+ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_Issue47017_Assert) {
+ // WARNING: This MUST be big enough for the serialised JSON string.
+ const int kBufferSize = 1024;
+ char buffer[kBufferSize];
+ const char* kScript =
+ "void foo(Object? bar) {\n"
+ " assert(bar == null);\n"
+ "}\n"
+ "void main() {\n"
+ " foo(null);\n"
+ "}\n";
+
+ Library& lib = Library::Handle();
+ const bool old_asserts = IsolateGroup::Current()->asserts();
+ IsolateGroup::Current()->set_asserts(true);
+ lib ^= ExecuteScript(kScript);
+ IsolateGroup::Current()->set_asserts(old_asserts);
+ ASSERT(!lib.IsNull());
+ const Script& script =
+ Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));
+
+ SourceReport report(SourceReport::kCoverage, SourceReport::kForceCompile);
+ JSONStream js;
+ report.PrintJSON(&js, script);
+ const char* json_str = js.ToCString();
+ ASSERT(strlen(json_str) < kBufferSize);
+ ElideJSONSubstring("classes", json_str, buffer);
+ ElideJSONSubstring("libraries", buffer, buffer);
+ EXPECT_STREQ(
+ "{\"type\":\"SourceReport\",\"ranges\":["
+
+ // Foo is hit, and the assert is hit.
+ "{\"scriptIndex\":0,\"startPos\":0,\"endPos\":47,\"compiled\":true,"
+ "\"coverage\":{\"hits\":[0,33],\"misses\":[]}},"
+
+ // Main is hit.
+ "{\"scriptIndex\":0,\"startPos\":49,\"endPos\":76,\"compiled\":true,"
+ "\"coverage\":{\"hits\":[49,65],\"misses\":[]}}],"
+
+ // Only one script in the script table.
+ "\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\","
+ "\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
+ buffer);
+}
+
#endif // !PRODUCT
} // namespace dart
diff --git a/runtime/vm/timeline.cc b/runtime/vm/timeline.cc
index ab0d62a..386178e 100644
--- a/runtime/vm/timeline.cc
+++ b/runtime/vm/timeline.cc
@@ -1441,7 +1441,9 @@
JSONArray events(js);
for (intptr_t i = 0; i < length(); i++) {
const TimelineEvent* event = At(i);
- events.AddValue(event);
+ if (event->IsValid()) {
+ events.AddValue(event);
+ }
}
}
#endif
diff --git a/tests/corelib/error_throw_with_stacktrace_test.dart b/tests/corelib/error_throw_with_stacktrace_test.dart
index 026df50..e74c80c 100644
--- a/tests/corelib/error_throw_with_stacktrace_test.dart
+++ b/tests/corelib/error_throw_with_stacktrace_test.dart
@@ -131,7 +131,7 @@
// Should work with whichever object gets here.
try {
Error.throwWithStackTrace(error, systemStack);
- } on Error catch (e, s) {
+ } catch (e, s) {
Expect.identical(error, e);
Expect.equals("$systemStack", "$s");
}
diff --git a/tests/corelib_2/error_throw_with_stacktrace_test.dart b/tests/corelib_2/error_throw_with_stacktrace_test.dart
index 303b06c..d6c3b59 100644
--- a/tests/corelib_2/error_throw_with_stacktrace_test.dart
+++ b/tests/corelib_2/error_throw_with_stacktrace_test.dart
@@ -132,7 +132,7 @@
// Should work with whichever object gets here.
try {
Error.throwWithStackTrace(error, systemStack);
- } on Error catch (e, s) {
+ } catch (e, s) {
Expect.identical(error, e);
Expect.equals("$systemStack", "$s");
}
diff --git a/tools/VERSION b/tools/VERSION
index 83662b0..b6a40f8 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 16
PATCH 0
-PRERELEASE 3
+PRERELEASE 4
PRERELEASE_PATCH 0
\ No newline at end of file