| // 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<char>(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<bool> 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<bool> 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<bool> 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<bool> 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<bool> 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<void*>(ptr), new_size); |
| return reinterpret_cast<uint8_t*>(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<char*>("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<char*>("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<int64_t>(isolate->main_port())); |
| param0_value.value.as_string = const_cast<char*>(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<char*>("step"); |
| param_keys_list[1] = ¶m1_name; |
| |
| Dart_CObject param1_value; |
| param1_value.type = Dart_CObject_kString; |
| param1_value.value.as_string = const_cast<char*>("Rewind"); |
| param_values_list[1] = ¶m1_value; |
| |
| Dart_CObject param2_name; |
| param2_name.type = Dart_CObject_kString; |
| param2_name.value.as_string = const_cast<char*>("frameIndex"); |
| param_keys_list[2] = ¶m2_name; |
| |
| Dart_CObject param2_value; |
| param2_value.type = Dart_CObject_kString; |
| param2_value.value.as_string = const_cast<char*>(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<bool> 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<bool> 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<bool> sfs1(&FLAG_trace_rewind, true); |
| SetFlagScope<bool> sfs2(&FLAG_prune_dead_locals, false); |
| SetFlagScope<bool> sfs3(&FLAG_enable_inlining_annotations, true); |
| SetFlagScope<bool> sfs4(&FLAG_background_compilation, false); |
| SetFlagScope<int> 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 |