// Copyright (c) 2011, 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/parser.h" #include "vm/ast_printer.h" #include "vm/class_finalizer.h" #include "vm/debugger.h" #include "vm/debugger_api_impl_test.h" #include "vm/longjump.h" #include "vm/object.h" #include "vm/symbols.h" #include "vm/thread.h" #include "vm/unit_test.h" namespace dart { DECLARE_FLAG(bool, show_invisible_frames); static void DumpFunction(const Library& lib, const char* cname, const char* fname) { const String& classname = String::Handle(Symbols::New(Thread::Current(), cname)); String& funcname = String::Handle(String::New(fname)); bool retval; EXPECT(Isolate::Current() != NULL); LongJumpScope jump; if (setjmp(*jump.Set()) == 0) { Class& cls = Class::Handle(lib.LookupClass(classname)); EXPECT(!cls.IsNull()); Function& function = Function::ZoneHandle(cls.LookupStaticFunction(funcname)); EXPECT(!function.IsNull()); ParsedFunction* parsed_function = new ParsedFunction(Thread::Current(), function); Parser::ParseFunction(parsed_function); EXPECT(parsed_function->node_sequence() != NULL); printf("Class %s function %s:\n", cname, fname); if (FLAG_support_ast_printer) { AstPrinter ast_printer; ast_printer.PrintFunctionNodes(*parsed_function); } else { OS::PrintErr("AST printer not supported."); } retval = true; } else { retval = false; } EXPECT(retval); } void CheckField(const Library& lib, const char* class_name, const char* field_name, bool expect_static, bool is_final) { const String& classname = String::Handle(Symbols::New(Thread::Current(), class_name)); Class& cls = Class::Handle(lib.LookupClass(classname)); EXPECT(!cls.IsNull()); String& fieldname = String::Handle(String::New(field_name)); String& functionname = String::Handle(); Function& function = Function::Handle(); Field& field = Field::Handle(); if (expect_static) { field ^= cls.LookupStaticFieldAllowPrivate(fieldname); functionname ^= Field::GetterName(fieldname); function ^= cls.LookupStaticFunction(functionname); EXPECT(function.IsNull()); functionname ^= Field::SetterName(fieldname); function ^= cls.LookupStaticFunction(functionname); EXPECT(function.IsNull()); } else { field ^= cls.LookupInstanceFieldAllowPrivate(fieldname); functionname ^= Field::GetterName(fieldname); function ^= cls.LookupDynamicFunction(functionname); EXPECT(!function.IsNull()); functionname ^= Field::SetterName(fieldname); function ^= cls.LookupDynamicFunction(functionname); EXPECT(is_final ? function.IsNull() : !function.IsNull()); } EXPECT(!field.IsNull()); EXPECT_EQ(field.is_static(), expect_static); } void CheckFunction(const Library& lib, const char* class_name, const char* function_name, bool expect_static) { const String& classname = String::Handle(Symbols::New(Thread::Current(), class_name)); Class& cls = Class::Handle(lib.LookupClass(classname)); EXPECT(!cls.IsNull()); String& functionname = String::Handle(String::New(function_name)); Function& function = Function::Handle(); if (expect_static) { function ^= cls.LookupStaticFunction(functionname); } else { function ^= cls.LookupDynamicFunction(functionname); } EXPECT(!function.IsNull()); } ISOLATE_UNIT_TEST_CASE(ParseClassDefinition) { const char* script_chars = "class C { } \n" "class A { \n" " var f0; \n" " int f1; \n" " final f2; \n" " final int f3, f4; \n" " static String s1, s2; \n" " static const int s3 = 8675309; \n" " static bar(i, [var d = 5]) { return 77; } \n" " static foo() native \"native_function_name\"; \n" "} \n"; String& url = String::Handle(String::New("dart-test:Parser_TopLevel")); String& source = String::Handle(String::New(script_chars)); Script& script = Script::Handle(Script::New(url, source, RawScript::kScriptTag)); Library& lib = Library::ZoneHandle(Library::CoreLibrary()); script.Tokenize(String::Handle(String::New(""))); Parser::ParseCompilationUnit(lib, script); EXPECT(ClassFinalizer::ProcessPendingClasses()); CheckField(lib, "A", "f1", false, false); CheckField(lib, "A", "f2", false, true); CheckField(lib, "A", "f3", false, true); CheckField(lib, "A", "f4", false, true); CheckField(lib, "A", "s1", true, false); CheckField(lib, "A", "s2", true, false); CheckField(lib, "A", "s3", true, true); CheckFunction(lib, "A", "bar", true); CheckFunction(lib, "A", "foo", true); } ISOLATE_UNIT_TEST_CASE(Parser_TopLevel) { const char* script_chars = "class A extends B { \n" " static bar(var i, [var d = 5]) { return 77; } \n" " static foo() { return 42; } \n" " static baz(var i) { var q = 5; return i + q; } \n" "} \n" " \n" "class B { \n" " static bam(k) { return A.foo(); } \n" "} \n"; String& url = String::Handle(String::New("dart-test:Parser_TopLevel")); String& source = String::Handle(String::New(script_chars)); Script& script = Script::Handle(Script::New(url, source, RawScript::kScriptTag)); Library& lib = Library::ZoneHandle(Library::CoreLibrary()); script.Tokenize(String::Handle(String::New(""))); Parser::ParseCompilationUnit(lib, script); EXPECT(ClassFinalizer::ProcessPendingClasses()); DumpFunction(lib, "A", "foo"); DumpFunction(lib, "A", "bar"); DumpFunction(lib, "A", "baz"); DumpFunction(lib, "B", "bam"); } #ifndef PRODUCT static char* saved_vars = NULL; static char* SkipIndex(const char* input) { char* output_buffer = new char[strlen(input)]; char* output = output_buffer; while (input[0] != '\0') { const char* index_pos = strstr(input, "index="); if (index_pos == NULL) { while (input[0] != '\0') { *output++ = *input++; } break; } // Copy prefix until "index=" while (input < index_pos) { *output++ = *input++; } // Skip until space. input += strcspn(input, " "); // Skip until next non-space. input += strspn(input, " "); } output[0] = '\0'; return output_buffer; } // Saves the var descriptors for all frames on the stack as a string. static void SaveVars(Dart_IsolateId isolate_id, intptr_t bp_id, const Dart_CodeLocation& loc) { TransitionNativeToVM transition(Thread::Current()); DebuggerStackTrace* stack = Isolate::Current()->debugger()->StackTrace(); intptr_t num_frames = stack->Length(); const int kBufferLen = 2048; char* buffer = reinterpret_cast(malloc(kBufferLen)); char* pos = buffer; LocalVarDescriptors& var_desc = LocalVarDescriptors::Handle(); for (intptr_t i = 0; i < num_frames; i++) { ActivationFrame* frame = stack->FrameAt(i); var_desc = frame->code().GetLocalVarDescriptors(); const char* var_str = SkipIndex(var_desc.ToCString()); const char* function_str = String::Handle(frame->function().QualifiedUserVisibleName()) .ToCString(); pos += Utils::SNPrint(pos, (kBufferLen - (pos - buffer)), "%s\n%s", function_str, var_str); delete[] var_str; } pos[0] = '\0'; if (saved_vars != NULL) { free(saved_vars); } saved_vars = buffer; } // Uses the debugger to pause the program and capture the variable // descriptors for all frames on the stack. static char* CaptureVarsAtLine(Dart_Handle lib, const char* entry, int line) { EXPECT(ClassFinalizer::ProcessPendingClasses()); bool saved_flag = FLAG_show_invisible_frames; FLAG_show_invisible_frames = true; Dart_SetPausedEventHandler(SaveVars); EXPECT_VALID(Dart_SetBreakpoint(NewString(TestCase::url()), line)); saved_vars = NULL; EXPECT_VALID(Dart_Invoke(lib, NewString(entry), 0, NULL)); char* tmp = saved_vars; saved_vars = NULL; FLAG_show_invisible_frames = saved_flag; return tmp; } TEST_CASE(Parser_AllocateVariables_CapturedVar) { const char* kScriptChars = "int main() {\n" " var value = 11;\n" " int f(var param) {\n" " return param + value;\n" // line 4 " }\n" " return f(22);\n" "}\n"; Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); EXPECT_VALID(lib); char* vars = CaptureVarsAtLine(lib, "main", 4); EXPECT_STREQ( // function f uses one ctx var at (0); doesn't save ctx. "main.f\n" " 0 ContextLevel level=0 begin=0 end=56\n" " 1 ContextVar level=0 begin=14 end=28 name=value\n" " 2 StackVar scope=1 begin=16 end=28 name=param\n" " 3 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" // Closure call saves current context. "_Closure.call\n" " 0 ContextLevel level=0 begin=0 end=12\n" " 1 StackVar scope=1 begin=-1 end=0 name=this\n" " 2 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" // function main uses one ctx var at (1); saves caller ctx. "main\n" " 0 ContextLevel level=0 begin=0 end=10\n" " 1 ContextLevel level=1 begin=12 end=20\n" " 2 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" " 3 ContextVar level=1 begin=10 end=38 name=value\n" " 4 StackVar scope=2 begin=12 end=38 name=f\n", vars); free(vars); } TEST_CASE(Parser_AllocateVariables_NestedCapturedVar) { const char* kScriptChars = "int a() {\n" " int b() {\n" " var value = 11;\n" " int c() {\n" " return value;\n" // line 5 " }\n" " return c();\n" " }\n" " return b();\n" "}\n"; Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); EXPECT_VALID(lib); char* vars = CaptureVarsAtLine(lib, "a", 5); EXPECT_STREQ( // Innermost function uses captured variable 'value' from middle // function. "a.b.c\n" " 0 ContextLevel level=0 begin=0 end=54\n" " 1 ContextVar level=0 begin=20 end=30 name=value\n" " 2 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" // Closure call saves current context. "_Closure.call\n" " 0 ContextLevel level=0 begin=0 end=12\n" " 1 StackVar scope=1 begin=-1 end=0 name=this\n" " 2 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" // Middle function saves the entry context. Notice that this // happens here and not in the outermost function. We always // save the entry context at the last possible moment. "a.b\n" " 0 ContextLevel level=0 begin=0 end=50\n" " 1 ContextLevel level=1 begin=52 end=60\n" " 2 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" " 3 ContextVar level=1 begin=16 end=38 name=value\n" " 4 StackVar scope=2 begin=18 end=38 name=c\n" // Closure call saves current context. "_Closure.call\n" " 0 ContextLevel level=0 begin=0 end=12\n" " 1 StackVar scope=1 begin=-1 end=0 name=this\n" " 2 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" // Outermost function neglects to save the entry context. We // don't save the entry context if the function has no captured // variables. "a\n" " 0 ContextLevel level=0 begin=0 end=18\n" " 1 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" " 2 StackVar scope=2 begin=6 end=46 name=b\n", vars); free(vars); } TEST_CASE(Parser_AllocateVariables_TwoChains) { const char* kScriptChars = "int a() {\n" " var value1 = 11;\n" " int b() {\n" " int aa() {\n" " var value2 = 12;\n" " int bb() {\n" " return value2;\n" // line 7 " }\n" " return bb();\n" " }\n" " return value1 + aa();\n" " }\n" " return b();\n" "}\n"; Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); EXPECT_VALID(lib); char* vars = CaptureVarsAtLine(lib, "a", 7); EXPECT_STREQ( // bb captures only value2 from aa. No others. "a.b.aa.bb\n" " 0 ContextLevel level=0 begin=0 end=54\n" " 1 ContextVar level=0 begin=35 end=46 name=value2\n" " 2 CurrentCtx scope=0 begin=0 end=0" " name=:current_context_var\n" // Closure call saves current context. "_Closure.call\n" " 0 ContextLevel level=0 begin=0 end=12\n" " 1 StackVar scope=1 begin=-1 end=0 name=this\n" " 2 CurrentCtx scope=0 begin=0 end=0" " name=:current_context_var\n" // aa shares value2. Notice that we save the entry ctx instead // of chaining from b. This keeps us from holding onto closures // that we would never access. "a.b.aa\n" " 0 ContextLevel level=0 begin=0 end=50\n" " 1 ContextLevel level=1 begin=52 end=60\n" " 2 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" " 3 ContextVar level=1 begin=30 end=55 name=value2\n" " 4 StackVar scope=2 begin=32 end=55 name=bb\n" // Closure call saves current context. "_Closure.call\n" " 0 ContextLevel level=0 begin=0 end=12\n" " 1 StackVar scope=1 begin=-1 end=0 name=this\n" " 2 CurrentCtx scope=0 begin=0 end=0" " name=:current_context_var\n" // b captures value1 from a. "a.b\n" " 0 ContextLevel level=0 begin=0 end=60\n" " 1 ContextVar level=0 begin=14 end=65 name=value1\n" " 2 CurrentCtx scope=0 begin=0 end=0" " name=:current_context_var\n" " 3 StackVar scope=2 begin=18 end=65 name=aa\n" // Closure call saves current context. "_Closure.call\n" " 0 ContextLevel level=0 begin=0 end=12\n" " 1 StackVar scope=1 begin=-1 end=0 name=this\n" " 2 CurrentCtx scope=0 begin=0 end=0" " name=:current_context_var\n" // a shares value1, saves entry ctx. "a\n" " 0 ContextLevel level=0 begin=0 end=10\n" " 1 ContextLevel level=1 begin=12 end=20\n" " 2 CurrentCtx scope=0 begin=0 end=0" " name=:current_context_var\n" " 3 ContextVar level=1 begin=10 end=73 name=value1\n" " 4 StackVar scope=2 begin=12 end=73 name=b\n", vars); free(vars); } TEST_CASE(Parser_AllocateVariables_Issue7681) { // This is a distilled version of the program from Issue 7681. // // When we create the closure at line 11, we need to make sure to // save the entry context instead of chaining to the parent context. // // This test is somewhat redundant with CapturedVarChain but // included for good measure. const char* kScriptChars = "class X {\n" " Function onX;\n" "}\n" "\n" "class Y {\n" " Function onY;\n" "}\n" "\n" "void doIt() {\n" " var x = new X();\n" " x.onX = (y) {\n" " y.onY = () {\n" // line 12 " return y;\n" " };\n" " };\n" " x.onX(new Y());\n" "}\n"; Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); EXPECT_VALID(lib); char* vars = CaptureVarsAtLine(lib, "doIt", 12); EXPECT_STREQ( // This frame saves the entry context instead of chaining. Good. "doIt.\n" " 0 ContextLevel level=0 begin=0 end=0\n" " 1 ContextLevel level=1 begin=48 end=56\n" " 2 ContextVar level=1 begin=44 end=67 name=y\n" " 3 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" // Closure call saves current context. "_Closure.call\n" " 0 ContextLevel level=0 begin=0 end=12\n" " 1 StackVar scope=1 begin=-1 end=0 name=this\n" " 2 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" "X.onX\n" " 0 ContextLevel level=0 begin=0 end=14\n" " 1 StackVar scope=1 begin=-1 end=0 name=this\n" " 2 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" // No context is saved here since no vars are captured. "doIt\n" " 0 ContextLevel level=0 begin=0 end=22\n" " 1 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" " 2 StackVar scope=2 begin=36 end=83 name=x\n", vars); free(vars); } TEST_CASE(Parser_AllocateVariables_CaptureLoopVar) { // This test verifies that... // // https://code.google.com/p/dart/issues/detail?id=18561 // // ...stays fixed. const char* kScriptChars = "int outer() {\n" " for(int i = 0; i < 1; i++) {\n" " var value = 11 + i;\n" " int inner() {\n" " return value;\n" // line 5 " }\n" " return inner();\n" " }\n" "}\n"; Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); EXPECT_VALID(lib); char* vars = CaptureVarsAtLine(lib, "outer", 5); EXPECT_STREQ( // inner function captures variable value. That's fine. "outer.inner\n" " 0 ContextLevel level=0 begin=0 end=54\n" " 1 ContextVar level=0 begin=34 end=44 name=value\n" " 2 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" // Closure call saves current context. "_Closure.call\n" " 0 ContextLevel level=0 begin=0 end=12\n" " 1 StackVar scope=1 begin=-1 end=0 name=this\n" " 2 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" // The outer function saves the entry context, even though the // captured variable is in a loop. Good. "outer\n" " 0 ContextLevel level=0 begin=0 end=12\n" " 1 ContextLevel level=1 begin=14 end=22\n" " 2 ContextLevel level=0 begin=24 end=38\n" " 3 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" " 4 StackVar scope=3 begin=12 end=53 name=i\n" " 5 ContextVar level=1 begin=29 end=53 name=value\n" " 6 StackVar scope=4 begin=31 end=53 name=inner\n", vars); free(vars); } TEST_CASE(Parser_AllocateVariables_MiddleChain) { const char* kScriptChars = "a() {\n" " int x = 11;\n" " b() {\n" " for (int i = 0; i < 1; i++) {\n" " int d() {\n" " return i;\n" " }\n" " }\n" " int c() {\n" " return x + 1;\n" // line 10 " }\n" " return c();\n" " }\n" " return b();\n" "}\n"; Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); EXPECT_VALID(lib); char* vars = CaptureVarsAtLine(lib, "a", 10); EXPECT_STREQ( "a.b.c\n" " 0 ContextLevel level=0 begin=0 end=56\n" " 1 ContextVar level=0 begin=52 end=65 name=x\n" " 2 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" "_Closure.call\n" " 0 ContextLevel level=0 begin=0 end=12\n" " 1 StackVar scope=1 begin=-1 end=0 name=this\n" " 2 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" // Doesn't save the entry context. Chains to parent instead. "a.b\n" " 0 ContextLevel level=0 begin=0 end=50\n" " 1 ContextLevel level=1 begin=52 end=76\n" " 2 ContextLevel level=0 begin=78 end=84\n" " 3 ContextVar level=0 begin=12 end=74 name=x\n" " 4 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" " 5 StackVar scope=2 begin=49 end=74 name=c\n" " 6 ContextVar level=1 begin=23 end=49 name=i\n" " 7 StackVar scope=4 begin=34 end=49 name=d\n" "_Closure.call\n" " 0 ContextLevel level=0 begin=0 end=12\n" " 1 StackVar scope=1 begin=-1 end=0 name=this\n" " 2 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" "a\n" " 0 ContextLevel level=0 begin=0 end=10\n" " 1 ContextLevel level=1 begin=12 end=20\n" " 2 CurrentCtx scope=0 begin=0 end=0 name=:current_context_var\n" " 3 ContextVar level=1 begin=9 end=82 name=x\n" " 4 StackVar scope=2 begin=11 end=82 name=b\n", vars); free(vars); } #endif // !PRODUCT } // namespace dart