blob: e7450b7d2abb3ef7cc4b7a781598c9563fb25a39 [file] [log] [blame]
// 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/ast_printer.h"
#include "vm/class_finalizer.h"
#include "vm/debugger.h"
#include "vm/longjump.h"
#include "vm/object.h"
#include "vm/parser.h"
#include "vm/symbols.h"
#include "vm/thread.h"
#include "vm/unit_test.h"
namespace dart {
DECLARE_FLAG(bool, show_invisible_frames);
void DumpFunction(const Library& lib, const char* cname, const char* fname) {
const String& classname = String::Handle(Symbols::New(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);
AstPrinter::PrintFunctionNodes(*parsed_function);
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(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.LookupStaticField(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.LookupInstanceField(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(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());
}
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);
}
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");
}
const char* saved_vars = NULL;
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) {
DebuggerStackTrace* stack =
Isolate::Current()->debugger()->StackTrace();
intptr_t num_frames = stack->Length();
const int kBufferLen = 2048;
char* buffer = new char[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 += OS::SNPrint(pos, (kBufferLen - (pos - buffer)),
"%s\n%s",
function_str,
var_str);
delete [] var_str;
}
pos[0] = '\0';
saved_vars = buffer;
}
// Uses the debugger to pause the program and capture the variable
// descriptors for all frames on the stack.
static const 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;
Isolate* isolate = Isolate::Current();
Debugger* debugger = isolate->debugger();
const String& url = String::Handle(String::New(TestCase::url()));
Dart_SetPausedEventHandler(SaveVars);
debugger->SetBreakpointAtLine(url, line);
saved_vars = NULL;
EXPECT_VALID(Dart_Invoke(lib, NewString(entry), 0, NULL));
const 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);
EXPECT_STREQ(
// function f uses one ctx var at (0); doesn't save ctx.
"main.f\n"
" 0 ContextVar level=0 begin=14 end=28 name=value\n"
" 1 StackVar scope=1 begin=16 end=28 name=param\n"
" 2 CurrentCtx scope=0 begin=0 end=0"
" name=:current_context_var\n"
// Closure call saves current context.
"_FunctionImpl.call\n"
" 0 StackVar scope=1 begin=0 end=4 name=this\n"
" 1 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 CurrentCtx scope=0 begin=0 end=0"
" name=:current_context_var\n"
" 1 ContextLevel level=1 scope=2 begin=4 end=37\n"
" 2 ContextVar level=1 begin=10 end=37 name=value\n"
" 3 StackVar scope=2 begin=12 end=37 name=f\n",
CaptureVarsAtLine(lib, "main", 4));
}
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);
EXPECT_STREQ(
// Innermost function uses captured variable 'value' from middle
// function.
"a.b.c\n"
" 0 ContextVar level=0 begin=20 end=30 name=value\n"
" 1 CurrentCtx scope=0 begin=0 end=0"
" name=:current_context_var\n"
// Closure call saves current context.
"_FunctionImpl.call\n"
" 0 StackVar scope=1 begin=0 end=4 name=this\n"
" 1 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 CurrentCtx scope=0 begin=0 end=0"
" name=:current_context_var\n"
" 1 ContextLevel level=1 scope=2 begin=10 end=38\n"
" 2 ContextVar level=1 begin=16 end=38 name=value\n"
" 3 StackVar scope=2 begin=18 end=38 name=c\n"
// Closure call saves current context.
"_FunctionImpl.call\n"
" 0 StackVar scope=1 begin=0 end=4 name=this\n"
" 1 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 CurrentCtx scope=0 begin=0 end=0"
" name=:current_context_var\n"
" 1 StackVar scope=2 begin=6 end=46 name=b\n",
CaptureVarsAtLine(lib, "a", 5));
}
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);
EXPECT_STREQ(
// bb captures only value2 from aa. No others.
"a.b.aa.bb\n"
" 0 ContextVar level=0 begin=33 end=43 name=value2\n"
" 1 CurrentCtx scope=0 begin=0 end=0"
" name=:current_context_var\n"
// Closure call saves current context.
"_FunctionImpl.call\n"
" 0 StackVar scope=1 begin=0 end=4 name=this\n"
" 1 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 CurrentCtx scope=0 begin=0 end=0"
" name=:current_context_var\n"
" 1 ContextLevel level=1 scope=2 begin=22 end=52\n"
" 2 ContextVar level=1 begin=28 end=52 name=value2\n"
" 3 StackVar scope=2 begin=30 end=52 name=bb\n"
// Closure call saves current context.
"_FunctionImpl.call\n"
" 0 StackVar scope=1 begin=0 end=4 name=this\n"
" 1 CurrentCtx scope=0 begin=0 end=0"
" name=:current_context_var\n"
// b captures value1 from a.
"a.b\n"
" 0 ContextVar level=0 begin=14 end=62 name=value1\n"
" 1 CurrentCtx scope=0 begin=0 end=0"
" name=:current_context_var\n"
" 2 StackVar scope=2 begin=18 end=62 name=aa\n"
// Closure call saves current context.
"_FunctionImpl.call\n"
" 0 StackVar scope=1 begin=0 end=4 name=this\n"
" 1 CurrentCtx scope=0 begin=0 end=0"
" name=:current_context_var\n"
// a shares value1, saves entry ctx.
"a\n"
" 0 CurrentCtx scope=0 begin=0 end=0"
" name=:current_context_var\n"
" 1 ContextLevel level=1 scope=2 begin=4 end=70\n"
" 2 ContextVar level=1 begin=10 end=70 name=value1\n"
" 3 StackVar scope=2 begin=12 end=70 name=b\n",
CaptureVarsAtLine(lib, "a", 7));
}
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);
EXPECT_STREQ(
// This frame saves the entry context instead of chaining. Good.
"doIt.<anonymous closure>\n"
" 0 ContextLevel level=1 scope=1 begin=41 end=62\n"
" 1 ContextVar level=1 begin=42 end=62 name=y\n"
" 2 CurrentCtx scope=0 begin=0 end=0"
" name=:current_context_var\n"
// Closure call saves current context.
"_FunctionImpl.call\n"
" 0 StackVar scope=1 begin=0 end=4 name=this\n"
" 1 CurrentCtx scope=0 begin=0 end=0"
" name=:current_context_var\n"
"X.onX\n"
" 0 StackVar scope=1 begin=0 end=0 name=this\n"
" 1 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 CurrentCtx scope=0 begin=0 end=0"
" name=:current_context_var\n"
" 1 StackVar scope=2 begin=35 end=77 name=x\n",
CaptureVarsAtLine(lib, "doIt", 12));
}
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);
EXPECT_STREQ(
// inner function captures variable value. That's fine.
"outer.inner\n"
" 0 ContextVar level=0 begin=32 end=42 name=value\n"
" 1 CurrentCtx scope=0 begin=0 end=0"
" name=:current_context_var\n"
// Closure call saves current context.
"_FunctionImpl.call\n"
" 0 StackVar scope=1 begin=0 end=4 name=this\n"
" 1 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 CurrentCtx scope=0 begin=0 end=0"
" name=:current_context_var\n"
" 1 StackVar scope=3 begin=12 end=50 name=i\n"
" 2 ContextLevel level=1 scope=4 begin=20 end=50\n"
" 3 ContextVar level=1 begin=28 end=50 name=value\n"
" 4 StackVar scope=4 begin=30 end=50 name=inner\n",
CaptureVarsAtLine(lib, "outer", 5));
}
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);
EXPECT_STREQ(
"a.b.c\n"
" 0 ContextVar level=0 begin=50 end=62 name=x\n"
" 1 CurrentCtx scope=0 begin=0 end=0"
" name=:current_context_var\n"
"_FunctionImpl.call\n"
" 0 StackVar scope=1 begin=0 end=4 name=this\n"
" 1 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 ContextVar level=0 begin=12 end=71 name=x\n"
" 1 CurrentCtx scope=0 begin=0 end=0"
" name=:current_context_var\n"
" 2 StackVar scope=2 begin=47 end=71 name=c\n"
" 3 ContextLevel level=1 scope=3 begin=18 end=47\n"
" 4 ContextVar level=1 begin=22 end=47 name=i\n"
" 5 StackVar scope=4 begin=32 end=47 name=d\n"
"_FunctionImpl.call\n"
" 0 StackVar scope=1 begin=0 end=4 name=this\n"
" 1 CurrentCtx scope=0 begin=0 end=0"
" name=:current_context_var\n"
"a\n"
" 0 CurrentCtx scope=0 begin=0 end=0"
" name=:current_context_var\n"
" 1 ContextLevel level=1 scope=2 begin=3 end=79\n"
" 2 ContextVar level=1 begin=9 end=79 name=x\n"
" 3 StackVar scope=2 begin=11 end=79 name=b\n",
CaptureVarsAtLine(lib, "a", 10));
}
} // namespace dart