// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "vm/debugger.h" #include "vm/dart_api_impl.h" #include "vm/dart_api_message.h" #include "vm/message.h" #include "vm/unit_test.h" namespace dart { #ifndef PRODUCT DECLARE_FLAG(bool, background_compilation); DECLARE_FLAG(bool, enable_inlining_annotations); DECLARE_FLAG(bool, prune_dead_locals); DECLARE_FLAG(bool, remove_script_timestamps_for_test); DECLARE_FLAG(bool, trace_rewind); DECLARE_FLAG(int, optimization_counter_threshold); // Search for the formatted string in buffer. // // TODO(turnidge): This function obscures the line number of failing // EXPECTs. Rework this. static void ExpectSubstringF(const char* buff, const char* fmt, ...) { va_list args; va_start(args, fmt); intptr_t len = OS::VSNPrint(NULL, 0, fmt, args); va_end(args); char* buffer = Thread::Current()->zone()->Alloc(len + 1); va_list args2; va_start(args2, fmt); OS::VSNPrint(buffer, (len + 1), fmt, args2); va_end(args2); EXPECT_SUBSTRING(buffer, buff); } TEST_CASE(Debugger_GetBreakpointsById) { const char* kScriptChars = "main() {\n" " var x = new StringBuffer();\n" " x.add('won');\n" " x.add('too');\n" " return x.toString();\n" "}\n"; SetFlagScope sfs(&FLAG_remove_script_timestamps_for_test, true); Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); EXPECT_VALID(lib); Isolate* isolate = Isolate::Current(); Debugger* debugger = isolate->debugger(); // Test with one loaded breakpoint, one latent breakpoint. Dart_Handle url = NewString(TestCase::url()); Dart_Handle result = Dart_SetBreakpoint(url, 2); EXPECT_VALID(result); EXPECT(Dart_IsInteger(result)); int64_t bp_id1 = 0; EXPECT_VALID(Dart_IntegerToInt64(result, &bp_id1)); result = Dart_SetBreakpoint(NewString("not_yet_loaded_script_uri"), 4); EXPECT_VALID(result); EXPECT(Dart_IsInteger(result)); int64_t bp_id2 = 0; EXPECT_VALID(Dart_IntegerToInt64(result, &bp_id2)); EXPECT(debugger->GetBreakpointById(bp_id1) != NULL); EXPECT(debugger->GetBreakpointById(bp_id2) != NULL); } static int closure_hit_count = 0; int64_t closure_bp_id[4]; static void PausedInClosuresHandler(Dart_IsolateId isolate_id, intptr_t bp_id, const Dart_CodeLocation& location) { EXPECT(bp_id == closure_bp_id[closure_hit_count]); closure_hit_count++; } TEST_CASE(Debugger_SetBreakpointInPartOfLibrary) { const char* kMainScript = "main() {}\n"; const char* kLib = "library test_lib;\n"; const char* kLibPart = "part of test_lib;\n" "void func(int a, int b) {\n" " return a + b;\n" "}\n"; SetFlagScope sfs(&FLAG_remove_script_timestamps_for_test, true); Dart_Handle root_lib = TestCase::LoadTestScript(kMainScript, NULL); EXPECT_VALID(root_lib); Dart_Handle url = NewString("test_lib_url"); Dart_Handle lib_source = NewString(kLib); Dart_Handle lib = Dart_LoadLibrary(url, Dart_Null(), lib_source, 0, 0); EXPECT_VALID(lib); EXPECT(Dart_IsLibrary(lib)); Dart_Handle part_url = NewString("part_url"); Dart_Handle part_source = NewString(kLibPart); Dart_Handle result = Dart_LoadSource(lib, part_url, Dart_Null(), part_source, 0, 0); EXPECT_VALID(result); EXPECT(Dart_IsLibrary(result)); EXPECT(Dart_IdentityEquals(lib, result)); result = Dart_SetBreakpoint(part_url, 3); EXPECT_VALID(result); EXPECT(Dart_IsInteger(result)); } TEST_CASE(Debugger_SetBreakpointInFunctionLiteralFieldInitializers) { const char* kScriptChars = "main() {\n" " var c = new MyClass();\n" " c.closure(1, 2);\n" " MyClass.staticClosure(7, 8);\n" " closure(3, 4);\n" " closureSingleLine(5, 6);\n" "}\n" "class MyClass {\n" " var closure = (int a, int b) {\n" " return a + b;\n" " };\n" " static var staticClosure = (int a, int b) {\n" " return a * b;\n" " };\n" "}\n" "var closure = (int a, int b) {\n" " return a + b;\n" "};\n" "var closureSingleLine = (int a, int b) => a * b;\n" "int v = 10;\n"; SetFlagScope sfs(&FLAG_remove_script_timestamps_for_test, true); Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); EXPECT_VALID(lib); Isolate* isolate = Isolate::Current(); Debugger* debugger = isolate->debugger(); Dart_Handle url = NewString(TestCase::url()); Dart_Handle result = Dart_SetBreakpoint(url, 10); EXPECT_VALID(result); EXPECT(Dart_IsInteger(result)); EXPECT_VALID(Dart_IntegerToInt64(result, &closure_bp_id[0])); result = Dart_SetBreakpoint(url, 13); EXPECT_VALID(result); EXPECT(Dart_IsInteger(result)); EXPECT_VALID(Dart_IntegerToInt64(result, &closure_bp_id[1])); result = Dart_SetBreakpoint(url, 17); EXPECT_VALID(result); EXPECT(Dart_IsInteger(result)); EXPECT_VALID(Dart_IntegerToInt64(result, &closure_bp_id[2])); result = Dart_SetBreakpoint(url, 19); EXPECT_VALID(result); EXPECT(Dart_IsInteger(result)); EXPECT_VALID(Dart_IntegerToInt64(result, &closure_bp_id[3])); // Cannot set breakpoint at the start of the class definition. // The implicit constructor token position matches that of the // class definition's token position. So, we do not want to // allow setting breakpoints in implicit consturctors. result = Dart_SetBreakpoint(url, 8); EXPECT_ERROR(result, "could not set breakpoint at line 8"); result = Dart_SetBreakpoint(url, 20); EXPECT_ERROR(result, "could not set breakpoint at line 20"); EXPECT(debugger->GetBreakpointById(closure_bp_id[0]) != NULL); EXPECT(debugger->GetBreakpointById(closure_bp_id[1]) != NULL); EXPECT(debugger->GetBreakpointById(closure_bp_id[2]) != NULL); EXPECT(debugger->GetBreakpointById(closure_bp_id[3]) != NULL); Dart_SetPausedEventHandler(PausedInClosuresHandler); result = Dart_Invoke(lib, NewString("main"), 0, NULL); EXPECT_VALID(result); EXPECT(closure_hit_count == 4); } TEST_CASE(Debugger_RemoveBreakpoint) { const char* kScriptChars = "main() {\n" " var x = new StringBuffer();\n" " x.add('won');\n" " x.add('too');\n" " return x.toString();\n" "}\n"; SetFlagScope sfs(&FLAG_remove_script_timestamps_for_test, true); Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); EXPECT_VALID(lib); Isolate* isolate = Isolate::Current(); Debugger* debugger = isolate->debugger(); // Test with one loaded breakpoint, one latent breakpoint. Dart_Handle url = NewString(TestCase::url()); Dart_Handle result = Dart_SetBreakpoint(url, 2); EXPECT_VALID(result); EXPECT(Dart_IsInteger(result)); int64_t bp_id1 = 0; EXPECT_VALID(Dart_IntegerToInt64(result, &bp_id1)); result = Dart_SetBreakpoint(NewString("not_yet_loaded_script_uri"), 4); EXPECT_VALID(result); EXPECT(Dart_IsInteger(result)); int64_t bp_id2 = 0; EXPECT_VALID(Dart_IntegerToInt64(result, &bp_id2)); EXPECT(debugger->GetBreakpointById(bp_id1) != NULL); EXPECT(debugger->GetBreakpointById(bp_id2) != NULL); debugger->RemoveBreakpoint(bp_id1); debugger->RemoveBreakpoint(bp_id2); EXPECT(debugger->GetBreakpointById(bp_id1) == NULL); EXPECT(debugger->GetBreakpointById(bp_id2) == NULL); } TEST_CASE(Debugger_PrintBreakpointsToJSONArray) { const char* kScriptChars = "main() {\n" " var x = new StringBuffer();\n" " x.add('won');\n" " x.add('too');\n" " return x.toString();\n" "}\n"; SetFlagScope sfs(&FLAG_remove_script_timestamps_for_test, true); Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); EXPECT_VALID(lib); Library& vmlib = Library::Handle(); vmlib ^= Api::UnwrapHandle(lib); EXPECT(!vmlib.IsNull()); const String& private_key = String::Handle(vmlib.private_key()); Isolate* isolate = Isolate::Current(); Debugger* debugger = isolate->debugger(); // Empty case. { JSONStream js; { JSONArray jsarr(&js); debugger->PrintBreakpointsToJSONArray(&jsarr); } EXPECT_STREQ("[]", js.ToCString()); } // Test with a couple of loaded breakpoints, one latent breakpoint. Dart_Handle url = NewString(TestCase::url()); EXPECT_VALID(Dart_SetBreakpoint(url, 2)); EXPECT_VALID(Dart_SetBreakpoint(url, 3)); EXPECT_VALID(Dart_SetBreakpoint(NewString("not_yet_loaded_script_uri"), 4)); { JSONStream js; { JSONArray jsarr(&js); debugger->PrintBreakpointsToJSONArray(&jsarr); } ExpectSubstringF( js.ToCString(), "[{\"type\":\"Breakpoint\",\"fixedId\":true,\"id\":\"breakpoints\\/" "2\",\"breakpointNumber\":2,\"resolved\":false,\"location\":{\"type\":" "\"UnresolvedSourceLocation\",\"script\":{\"type\":\"@Script\"," "\"fixedId\":true,\"id\":\"libraries\\/%s\\/scripts\\/" "test-lib\\/" "0\",\"uri\":\"test-lib\",\"_kind\":\"script\"},\"line\":3}},{\"type\":" "\"Breakpoint\",\"fixedId\":true,\"id\":\"breakpoints\\/" "1\",\"breakpointNumber\":1,\"resolved\":false,\"location\":{\"type\":" "\"UnresolvedSourceLocation\",\"script\":{\"type\":\"@Script\"," "\"fixedId\":true,\"id\":\"libraries\\/%s\\/scripts\\/" "test-lib\\/" "0\",\"uri\":\"test-lib\",\"_kind\":\"script\"},\"line\":2}},{\"type\":" "\"Breakpoint\",\"fixedId\":true,\"id\":\"breakpoints\\/" "3\",\"breakpointNumber\":3,\"resolved\":false,\"location\":{\"type\":" "\"UnresolvedSourceLocation\",\"scriptUri\":\"not_yet_loaded_script_" "uri\",\"line\":4}}]", private_key.ToCString(), private_key.ToCString()); } } static bool saw_paused_event = false; static void InspectPausedEvent(Dart_IsolateId isolate_id, intptr_t bp_id, const Dart_CodeLocation& loc) { Isolate* isolate = Isolate::Current(); Debugger* debugger = isolate->debugger(); // The debugger knows that it is paused, and why. EXPECT(debugger->IsPaused()); const ServiceEvent* event = debugger->PauseEvent(); EXPECT(event != NULL); EXPECT(event->kind() == ServiceEvent::kPauseBreakpoint); saw_paused_event = true; } TEST_CASE(Debugger_PauseEvent) { const char* kScriptChars = "main() {\n" " var x = new StringBuffer();\n" " x.write('won');\n" " x.write('too');\n" " return x.toString();\n" "}\n"; Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); EXPECT_VALID(lib); Isolate* isolate = Isolate::Current(); Debugger* debugger = isolate->debugger(); // No pause event. EXPECT(!debugger->IsPaused()); EXPECT(debugger->PauseEvent() == NULL); saw_paused_event = false; Dart_SetPausedEventHandler(InspectPausedEvent); // Set a breakpoint and run. EXPECT_VALID(Dart_SetBreakpoint(NewString(TestCase::url()), 2)); Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL); EXPECT_VALID(result); EXPECT(Dart_IsString(result)); // We ran the code in InspectPausedEvent. EXPECT(saw_paused_event); } static uint8_t* malloc_allocator(uint8_t* ptr, intptr_t old_size, intptr_t new_size) { void* new_ptr = realloc(reinterpret_cast(ptr), new_size); return reinterpret_cast(new_ptr); } const char* rewind_frame_index = "-1"; // Build and send a fake resume OOB message for testing purposes. void SendResumeMessage(Isolate* isolate) { // Format is: [ oob_type, port, seq, method_name, [keys], [values] ] Dart_CObject msg; Dart_CObject* list_values[6]; msg.type = Dart_CObject_kArray; msg.value.as_array.length = 6; msg.value.as_array.values = list_values; Dart_CObject oob; oob.type = Dart_CObject_kInt32; oob.value.as_int32 = Message::kServiceOOBMsg; list_values[0] = &oob; Dart_CObject reply_port; reply_port.type = Dart_CObject_kNull; list_values[1] = &reply_port; Dart_CObject seq; seq.type = Dart_CObject_kNull; list_values[2] = &seq; Dart_CObject method_name; method_name.type = Dart_CObject_kString; method_name.value.as_string = const_cast("resume"); list_values[3] = &method_name; const int kParamCount = 3; Dart_CObject param_keys; Dart_CObject* param_keys_list[kParamCount]; param_keys.type = Dart_CObject_kArray; param_keys.value.as_array.values = param_keys_list; param_keys.value.as_array.length = kParamCount; list_values[4] = ¶m_keys; Dart_CObject param_values; Dart_CObject* param_values_list[kParamCount]; param_values.type = Dart_CObject_kArray; param_values.value.as_array.values = param_values_list; param_values.value.as_array.length = kParamCount; list_values[5] = ¶m_values; Dart_CObject param0_name; param0_name.type = Dart_CObject_kString; param0_name.value.as_string = const_cast("isolateId"); param_keys_list[0] = ¶m0_name; Dart_CObject param0_value; param0_value.type = Dart_CObject_kString; const char* isolate_id = Thread::Current()->zone()->PrintToString( ISOLATE_SERVICE_ID_FORMAT_STRING, static_cast(isolate->main_port())); param0_value.value.as_string = const_cast(isolate_id); param_values_list[0] = ¶m0_value; Dart_CObject param1_name; param1_name.type = Dart_CObject_kString; param1_name.value.as_string = const_cast("step"); param_keys_list[1] = ¶m1_name; Dart_CObject param1_value; param1_value.type = Dart_CObject_kString; param1_value.value.as_string = const_cast("Rewind"); param_values_list[1] = ¶m1_value; Dart_CObject param2_name; param2_name.type = Dart_CObject_kString; param2_name.value.as_string = const_cast("frameIndex"); param_keys_list[2] = ¶m2_name; Dart_CObject param2_value; param2_value.type = Dart_CObject_kString; param2_value.value.as_string = const_cast(rewind_frame_index); param_values_list[2] = ¶m2_value; { uint8_t* buffer = NULL; ApiMessageWriter writer(&buffer, &malloc_allocator); bool success = writer.WriteCMessage(&msg); ASSERT(success); // Post the message at the given port. success = PortMap::PostMessage(new Message(isolate->main_port(), buffer, writer.BytesWritten(), Message::kOOBPriority)); ASSERT(success); } } static void RewindOnce(Dart_IsolateId isolate_id, intptr_t bp_id, const Dart_CodeLocation& loc) { bool first_time = !saw_paused_event; saw_paused_event = true; if (first_time) { Thread* T = Thread::Current(); Isolate* I = T->isolate(); // TODO(turnidge): It is weird that the isolate can get to this // point in our tests without being marked runnable. Clear this up // at some point. I->set_is_runnable(true); SendResumeMessage(I); I->PauseEventHandler(); } } TEST_CASE(Debugger_RewindOneFrame_Unoptimized) { SetFlagScope sfs(&FLAG_trace_rewind, true); // These variables are global state used by RewindOnce. saw_paused_event = false; rewind_frame_index = "1"; const char* kScriptChars = "import 'dart:developer';\n" "\n" "var msg = new StringBuffer();\n" "\n" "foo() {\n" " msg.write('enter(foo) ');\n" " debugger();\n" " msg.write('exit(foo) ');\n" "}\n" "\n" "main() {\n" " msg.write('enter(main) ');\n" " foo();\n" " msg.write('exit(main) ');\n" " return msg.toString();\n" "}\n"; Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); EXPECT_VALID(lib); Dart_SetPausedEventHandler(RewindOnce); Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL); const char* result_cstr; EXPECT_VALID(result); EXPECT(Dart_IsString(result)); EXPECT_VALID(Dart_StringToCString(result, &result_cstr)); EXPECT_STREQ("enter(main) enter(foo) enter(foo) exit(foo) exit(main) ", result_cstr); EXPECT(saw_paused_event); } TEST_CASE(Debugger_RewindTwoFrames_Unoptimized) { SetFlagScope sfs(&FLAG_trace_rewind, true); // These variables are global state used by RewindOnce. saw_paused_event = false; rewind_frame_index = "2"; const char* kScriptChars = "import 'dart:developer';\n" "\n" "var msg = new StringBuffer();\n" "\n" "foo() {\n" " msg.write('enter(foo) ');\n" " debugger();\n" " msg.write('exit(foo) ');\n" "}\n" "\n" "bar() {\n" " msg.write('enter(bar) ');\n" " foo();\n" " msg.write('exit(bar) ');\n" "}\n" "\n" "main() {\n" " msg.write('enter(main) ');\n" " bar();\n" " msg.write('exit(main) ');\n" " return msg.toString();\n" "}\n"; Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); EXPECT_VALID(lib); Dart_SetPausedEventHandler(RewindOnce); Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL); const char* result_cstr; EXPECT_VALID(result); EXPECT(Dart_IsString(result)); EXPECT_VALID(Dart_StringToCString(result, &result_cstr)); EXPECT_STREQ( "enter(main) enter(bar) enter(foo) enter(bar) enter(foo) " "exit(foo) exit(bar) exit(main) ", result_cstr); EXPECT(saw_paused_event); } TEST_CASE(Debugger_Rewind_Optimized) { SetFlagScope sfs1(&FLAG_trace_rewind, true); SetFlagScope sfs2(&FLAG_prune_dead_locals, false); SetFlagScope sfs3(&FLAG_enable_inlining_annotations, true); SetFlagScope sfs4(&FLAG_background_compilation, false); SetFlagScope sfs5(&FLAG_optimization_counter_threshold, 10); // These variables are global state used by RewindOnce. saw_paused_event = false; rewind_frame_index = "2"; const char* kScriptChars = "import 'dart:developer';\n" "\n" "const alwaysInline = \"AlwaysInline\";\n" "const noInline = \"NeverInline\";\n" "\n" "var msg = new StringBuffer();\n" "int i;\n" "\n" "@noInline\n" "foo() {\n" " msg.write('enter(foo) ');\n" " if (i > 15) {\n" " debugger();\n" " msg.write('exit(foo) ');\n" " return true;\n" " } else {\n" " msg.write('exit(foo) ');\n" " return false;\n" " }\n" "}\n" "\n" "@alwaysInline\n" "bar3() {\n" " msg.write('enter(bar3) ');\n" " var result = foo();\n" " msg.write('exit(bar3) ');\n" " return result;\n" "}\n" "\n" "@alwaysInline\n" "bar2() {\n" " msg.write('enter(bar2) ');\n" " var result = bar3();\n" " msg.write('exit(bar2) ');\n" " return result;\n" "}\n" "\n" "@alwaysInline\n" "bar1() {\n" " msg.write('enter(bar1) ');\n" " var result = bar2();\n" " msg.write('exit(bar1) ');\n" " return result;\n" "}\n" "\n" "main() {\n" " for (i = 0; i < 20; i++) {\n" " msg.clear();\n" " if (bar1()) break;\n;" " }\n" " return msg.toString();\n" "}\n"; Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); EXPECT_VALID(lib); Dart_SetPausedEventHandler(RewindOnce); Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL); const char* result_cstr; EXPECT_VALID(result); EXPECT(Dart_IsString(result)); EXPECT_VALID(Dart_StringToCString(result, &result_cstr)); EXPECT_STREQ( "enter(bar1) enter(bar2) enter(bar3) enter(foo) " "enter(bar3) enter(foo) " "exit(foo) exit(bar3) exit(bar2) exit(bar1) ", result_cstr); EXPECT(saw_paused_event); } #endif // !PRODUCT } // namespace dart