blob: f53745801d500554f34deb2fe23ad5f5314e9acf [file] [log] [blame]
// Copyright (c) 2012, 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/stack_frame.h"
#include "include/dart_api.h"
#include "platform/assert.h"
#include "vm/class_finalizer.h"
#include "vm/compiler/jit/compiler.h"
#include "vm/dart_api_impl.h"
#include "vm/dart_entry.h"
#include "vm/heap/verifier.h"
#include "vm/resolver.h"
#include "vm/unit_test.h"
#include "vm/zone.h"
namespace dart {
// Unit test for empty stack frame iteration.
ISOLATE_UNIT_TEST_CASE(EmptyStackFrameIteration) {
StackFrameIterator iterator(ValidationPolicy::kValidateFrames,
Thread::Current(),
StackFrameIterator::kNoCrossThreadIteration);
EXPECT(!iterator.HasNextFrame());
EXPECT(iterator.NextFrame() == NULL);
VerifyPointersVisitor::VerifyPointers();
}
// Unit test for empty dart stack frame iteration.
ISOLATE_UNIT_TEST_CASE(EmptyDartStackFrameIteration) {
DartFrameIterator iterator(Thread::Current(),
StackFrameIterator::kNoCrossThreadIteration);
EXPECT(iterator.NextFrame() == NULL);
VerifyPointersVisitor::VerifyPointers();
}
#define FUNCTION_NAME(name) StackFrame_##name
#define REGISTER_FUNCTION(name, count) {"" #name, FUNCTION_NAME(name), count},
void FUNCTION_NAME(StackFrame_equals)(Dart_NativeArguments args) {
NativeArguments* arguments = reinterpret_cast<NativeArguments*>(args);
TransitionNativeToVM transition(arguments->thread());
Zone* zone = arguments->thread()->zone();
const Instance& expected =
Instance::CheckedHandle(zone, arguments->NativeArgAt(0));
const Instance& actual =
Instance::CheckedHandle(zone, arguments->NativeArgAt(1));
if (!expected.OperatorEquals(actual)) {
OS::PrintErr("expected: '%s' actual: '%s'\n", expected.ToCString(),
actual.ToCString());
EXPECT(false);
}
}
void FUNCTION_NAME(StackFrame_frameCount)(Dart_NativeArguments args) {
NativeArguments* arguments = reinterpret_cast<NativeArguments*>(args);
TransitionNativeToVM transition(arguments->thread());
int count = 0;
StackFrameIterator frames(ValidationPolicy::kValidateFrames,
arguments->thread(),
StackFrameIterator::kNoCrossThreadIteration);
while (frames.NextFrame() != NULL) {
count += 1; // Count the frame.
}
VerifyPointersVisitor::VerifyPointers();
arguments->SetReturn(Object::Handle(Smi::New(count)));
}
void FUNCTION_NAME(StackFrame_dartFrameCount)(Dart_NativeArguments args) {
TransitionNativeToVM transition(Thread::Current());
int count = 0;
DartFrameIterator frames(Thread::Current(),
StackFrameIterator::kNoCrossThreadIteration);
while (frames.NextFrame() != NULL) {
count += 1; // Count the dart frame.
}
VerifyPointersVisitor::VerifyPointers();
NativeArguments* arguments = reinterpret_cast<NativeArguments*>(args);
arguments->SetReturn(Object::Handle(Smi::New(count)));
}
void FUNCTION_NAME(StackFrame_validateFrame)(Dart_NativeArguments args) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
Dart_Handle index = Dart_GetNativeArgument(args, 0);
Dart_Handle name = Dart_GetNativeArgument(args, 1);
TransitionNativeToVM transition(thread);
const Smi& frame_index_smi =
Smi::CheckedHandle(zone, Api::UnwrapHandle(index));
const char* expected_name =
String::CheckedHandle(zone, Api::UnwrapHandle(name)).ToCString();
int frame_index = frame_index_smi.Value();
int count = 0;
DartFrameIterator frames(thread, StackFrameIterator::kNoCrossThreadIteration);
StackFrame* frame = frames.NextFrame();
while (frame != NULL) {
if (count == frame_index) {
// Find the function corresponding to this frame and check if it
// matches the function name passed in.
const Function& function =
Function::Handle(zone, frame->LookupDartFunction());
if (function.IsNull()) {
FATAL("StackFrame_validateFrame fails, invalid dart frame.\n");
}
const char* name = function.ToFullyQualifiedCString();
// Currently all unit tests are loaded as being part of dart:core-lib.
String& url = String::Handle(zone, String::New(TestCase::url()));
const Library& lib =
Library::Handle(zone, Library::LookupLibrary(thread, url));
ASSERT(!lib.IsNull());
const char* lib_name = String::Handle(zone, lib.url()).ToCString();
char* full_name = OS::SCreate(zone, "%s_%s", lib_name, expected_name);
EXPECT_STREQ(full_name, name);
return;
}
count += 1; // Count the dart frames.
frame = frames.NextFrame();
}
FATAL("StackFrame_validateFrame fails, frame count < index passed in.\n");
}
// List all native functions implemented in the vm or core boot strap dart
// libraries so that we can resolve the native function to it's entry
// point.
#define STACKFRAME_NATIVE_LIST(V) \
V(StackFrame_equals, 2) \
V(StackFrame_frameCount, 0) \
V(StackFrame_dartFrameCount, 0) \
V(StackFrame_validateFrame, 2)
static struct NativeEntries {
const char* name_;
Dart_NativeFunction function_;
int argument_count_;
} BuiltinEntries[] = {STACKFRAME_NATIVE_LIST(REGISTER_FUNCTION)};
static Dart_NativeFunction native_lookup(Dart_Handle name,
int argument_count,
bool* auto_setup_scope) {
ASSERT(auto_setup_scope != NULL);
*auto_setup_scope = false;
TransitionNativeToVM transition(Thread::Current());
const Object& obj = Object::Handle(Api::UnwrapHandle(name));
ASSERT(obj.IsString());
const char* function_name = obj.ToCString();
ASSERT(function_name != NULL);
int num_entries = sizeof(BuiltinEntries) / sizeof(struct NativeEntries);
for (int i = 0; i < num_entries; i++) {
struct NativeEntries* entry = &(BuiltinEntries[i]);
if ((strcmp(function_name, entry->name_) == 0) &&
(entry->argument_count_ == argument_count)) {
return reinterpret_cast<Dart_NativeFunction>(entry->function_);
}
}
return NULL;
}
// Unit test case to verify stack frame iteration.
TEST_CASE(ValidateStackFrameIteration) {
const char* nullable_tag = TestCase::NullableTag();
// clang-format off
auto kScriptChars = Utils::CStringUniquePtr(
OS::SCreate(
nullptr,
"class StackFrame {"
" @pragma('vm:external-name', 'StackFrame_equals')\n"
" external static equals(var obj1, var obj2);\n"
" @pragma('vm:external-name', 'StackFrame_frameCount')\n"
" external static int frameCount();\n"
" @pragma('vm:external-name', 'StackFrame_dartFrameCount')\n"
" external static int dartFrameCount();\n"
" @pragma('vm:external-name', 'StackFrame_validateFrame')\n"
" external static validateFrame(int index, String name);"
"} "
"class First {"
" First() { }"
" int%s method1(int%s param) {"
" if (param == 1) {"
" param = method2(200);"
" } else {"
" param = method2(100);"
" }"
" }"
" int%s method2(int param) {"
" if (param == 200) {"
" First.staticmethod(this, param);"
" } else {"
" First.staticmethod(this, 10);"
" }"
" }"
" static int%s staticmethod(First obj, int param) {"
" if (param == 10) {"
" obj.method3(10);"
" } else {"
" obj.method3(200);"
" }"
" }"
" method3(int param) {"
" StackFrame.equals(9, StackFrame.frameCount());"
" StackFrame.equals(7, StackFrame.dartFrameCount());"
" StackFrame.validateFrame(0, \"StackFrame_validateFrame\");"
" StackFrame.validateFrame(1, \"First_method3\");"
" StackFrame.validateFrame(2, \"First_staticmethod\");"
" StackFrame.validateFrame(3, \"First_method2\");"
" StackFrame.validateFrame(4, \"First_method1\");"
" StackFrame.validateFrame(5, \"Second_method1\");"
" StackFrame.validateFrame(6, \"StackFrameTest_testMain\");"
" }"
"}"
"class Second {"
" Second() { }"
" int%s method1(int%s param) {"
" if (param == 1) {"
" param = method2(200);"
" } else {"
" First obj = new First();"
" param = obj.method1(1);"
" param = obj.method1(2);"
" }"
" }"
" int%s method2(int param) {"
" Second.staticmethod(this, param);"
" }"
" static int%s staticmethod(Second obj, int param) {"
" obj.method3(10);"
" }"
" method3(int param) {"
" StackFrame.equals(8, StackFrame.frameCount());"
" StackFrame.equals(6, StackFrame.dartFrameCount());"
" StackFrame.validateFrame(0, \"StackFrame_validateFrame\");"
" StackFrame.validateFrame(1, \"Second_method3\");"
" StackFrame.validateFrame(2, \"Second_staticmethod\");"
" StackFrame.validateFrame(3, \"Second_method2\");"
" StackFrame.validateFrame(4, \"Second_method1\");"
" StackFrame.validateFrame(5, \"StackFrameTest_testMain\");"
" }"
"}"
"class StackFrameTest {"
" static testMain() {"
" Second obj = new Second();"
" obj.method1(1);"
" obj.method1(2);"
" }"
"}",
nullable_tag, nullable_tag, nullable_tag, nullable_tag, nullable_tag,
nullable_tag, nullable_tag, nullable_tag),
std::free);
// clang-format on
Dart_Handle lib = TestCase::LoadTestScript(
kScriptChars.get(),
reinterpret_cast<Dart_NativeEntryResolver>(native_lookup));
Dart_Handle cls = Dart_GetClass(lib, NewString("StackFrameTest"));
EXPECT_VALID(Dart_Invoke(cls, NewString("testMain"), 0, NULL));
}
// Unit test case to verify stack frame iteration.
TEST_CASE(ValidateNoSuchMethodStackFrameIteration) {
const char* kScriptChars;
// The true stack depends on which strategy we are using for noSuchMethod. The
// stacktrace as seen by Dart is the same either way because dispatcher
// methods are marked invisible.
if (FLAG_lazy_dispatchers) {
kScriptChars =
"class StackFrame {"
" @pragma('vm:external-name', 'StackFrame_equals')\n"
" external static equals(var obj1, var obj2);\n"
" @pragma('vm:external-name', 'StackFrame_frameCount')\n"
" external static int frameCount();\n"
" @pragma('vm:external-name', 'StackFrame_dartFrameCount')\n"
" external static int dartFrameCount();\n"
" @pragma('vm:external-name', 'StackFrame_validateFrame')\n"
" external static validateFrame(int index, String name);"
"} "
"class StackFrame2Test {"
" StackFrame2Test() {}"
" noSuchMethod(Invocation im) {"
" /* We should have 6 general frames and 4 dart frames as follows:"
" * exit frame"
" * dart frame corresponding to StackFrame.frameCount"
" * dart frame corresponding to StackFrame2Test.noSuchMethod"
" * frame for instance function invocation stub calling "
"noSuchMethod"
" * dart frame corresponding to StackFrame2Test.testMain"
" * entry frame"
" */"
" StackFrame.equals(6, StackFrame.frameCount());"
" StackFrame.equals(4, StackFrame.dartFrameCount());"
" StackFrame.validateFrame(0, \"StackFrame_validateFrame\");"
" StackFrame.validateFrame(1, \"StackFrame2Test_noSuchMethod\");"
" StackFrame.validateFrame(2, \"StackFrame2Test_foo\");"
" StackFrame.validateFrame(3, \"StackFrame2Test_testMain\");"
" return 5;"
" }"
" static testMain() {"
" /* Declare |obj| dynamic so that noSuchMethod can be"
" * called in strong mode. */"
" dynamic obj = new StackFrame2Test();"
" StackFrame.equals(5, obj.foo(101, 202));"
" }"
"}";
} else {
kScriptChars =
"class StackFrame {"
" @pragma('vm:external-name', 'StackFrame_equals')\n"
" external static equals(var obj1, var obj2);\n"
" @pragma('vm:external-name', 'StackFrame_frameCount')\n"
" external static int frameCount();\n"
" @pragma('vm:external-name', 'StackFrame_dartFrameCount')\n"
" external static int dartFrameCount();\n"
" @pragma('vm:external-name', 'StackFrame_validateFrame')\n"
" external static validateFrame(int index, String name);"
"} "
"class StackFrame2Test {"
" StackFrame2Test() {}"
" noSuchMethod(Invocation im) {"
" /* We should have 8 general frames and 3 dart frames as follows:"
" * exit frame"
" * dart frame corresponding to StackFrame.frameCount"
" * dart frame corresponding to StackFrame2Test.noSuchMethod"
" * entry frame"
" * exit frame (call to runtime NoSuchMethodFromCallStub)"
" * IC stub"
" * dart frame corresponding to StackFrame2Test.testMain"
" * entry frame"
" */"
" StackFrame.equals(8, StackFrame.frameCount());"
" StackFrame.equals(3, StackFrame.dartFrameCount());"
" StackFrame.validateFrame(0, \"StackFrame_validateFrame\");"
" StackFrame.validateFrame(1, \"StackFrame2Test_noSuchMethod\");"
" StackFrame.validateFrame(2, \"StackFrame2Test_testMain\");"
" return 5;"
" }"
" static testMain() {"
" /* Declare |obj| dynamic so that noSuchMethod can be"
" * called in strong mode. */"
" dynamic obj = new StackFrame2Test();"
" StackFrame.equals(5, obj.foo(101, 202));"
" }"
"}";
}
Dart_Handle lib = TestCase::LoadTestScript(
kScriptChars, reinterpret_cast<Dart_NativeEntryResolver>(native_lookup));
Dart_Handle cls = Dart_GetClass(lib, NewString("StackFrame2Test"));
EXPECT_VALID(Dart_Invoke(cls, NewString("testMain"), 0, NULL));
}
} // namespace dart