| // 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/flags.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() == nullptr); | |
| VerifyPointersVisitor::VerifyPointers("EmptyStackFrameIterationTest"); | |
| } | |
| // Unit test for empty dart stack frame iteration. | |
| ISOLATE_UNIT_TEST_CASE(EmptyDartStackFrameIteration) { | |
| DartFrameIterator iterator(Thread::Current(), | |
| StackFrameIterator::kNoCrossThreadIteration); | |
| EXPECT(iterator.NextFrame() == nullptr); | |
| VerifyPointersVisitor::VerifyPointers("EmptyDartStackFrameIterationTest"); | |
| } | |
| #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() != nullptr) { | |
| count += 1; // Count the frame. | |
| } | |
| VerifyPointersVisitor::VerifyPointers("StackFrame_frameCount_Test"); | |
| 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() != nullptr) { | |
| count += 1; // Count the dart frame. | |
| } | |
| VerifyPointersVisitor::VerifyPointers("StackFrame_dartFrameCount_Test"); | |
| 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 != nullptr) { | |
| 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 != nullptr); | |
| *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 != nullptr); | |
| 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 nullptr; | |
| } | |
| #define STACK_FRAME_DEFN \ | |
| "class StackFrame {" \ | |
| " @pragma('vm:external-name', 'StackFrame_equals')\n" \ | |
| " external static equals(obj1, 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);" \ | |
| "}" | |
| // Unit test case to verify stack frame iteration. | |
| TEST_CASE(ValidateStackFrameIteration) { | |
| // clang-format off | |
| const char* kScriptChars = | |
| STACK_FRAME_DEFN | |
| "class First {" | |
| " First() { }" | |
| " int? method1(int? param) {" | |
| " if (param == 1) {" | |
| " param = method2(200);" | |
| " } else {" | |
| " param = method2(100);" | |
| " }" | |
| " }" | |
| " int? method2(int param) {" | |
| " if (param == 200) {" | |
| " First.staticmethod(this, param);" | |
| " } else {" | |
| " First.staticmethod(this, 10);" | |
| " }" | |
| " }" | |
| " static int? 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? method1(int? param) {" | |
| " if (param == 1) {" | |
| " param = method2(200);" | |
| " } else {" | |
| " First obj = new First();" | |
| " param = obj.method1(1);" | |
| " param = obj.method1(2);" | |
| " }" | |
| " }" | |
| " int? method2(int param) {" | |
| " Second.staticmethod(this, param);" | |
| " }" | |
| " static int? 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\");" | |
| " }" | |
| "}" | |
| "@pragma('vm:entry-point')\n" | |
| "class StackFrameTest {" | |
| " @pragma('vm:entry-point', 'call')\n" | |
| " static testMain() {" | |
| " Second obj = new Second();" | |
| " obj.method1(1);" | |
| " obj.method1(2);" | |
| " }" | |
| "}"; | |
| // clang-format on | |
| Dart_Handle lib = TestCase::LoadTestScript( | |
| kScriptChars, reinterpret_cast<Dart_NativeEntryResolver>(native_lookup)); | |
| Dart_Handle cls = Dart_GetClass(lib, NewString("StackFrameTest")); | |
| EXPECT_VALID(Dart_Invoke(cls, NewString("testMain"), 0, nullptr)); | |
| } | |
| TEST_CASE(ValidateNoSuchMethodStackFrameIteration) { | |
| const char* const kScriptChars = STACK_FRAME_DEFN | |
| "@pragma('vm:entry-point')\n" | |
| "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;" | |
| " }" | |
| " @pragma('vm:entry-point', 'call')\n" | |
| " 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));" | |
| " }" | |
| "}"; | |
| #if !defined(PRODUCT) | |
| SetFlagScope<bool> sfs(&FLAG_interpreter, false); | |
| #endif | |
| 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, nullptr)); | |
| } | |
| #if defined(DART_DYNAMIC_MODULES) | |
| TEST_CASE(ValidateNoSuchMethodStackFrameIterationInterpreted) { | |
| const char* const kScriptChars = STACK_FRAME_DEFN | |
| "@pragma('vm:entry-point')\n" | |
| "class StackFrame2Test {" | |
| " StackFrame2Test() {}" | |
| " noSuchMethod(Invocation im) {" | |
| " /* We should have 11 general frames and 5 dart frames as follows:" | |
| " * interpreter exit frame" | |
| " * dart frame corresponding to StackFrame.<method>" | |
| " * dart frame corresponding to StackFrame2Test.noSuchMethod" | |
| " * interpreter enter frame" | |
| " * stub exit frame" | |
| " * dart frame for unoptimized _objectNoSuchMethod" | |
| " * stub enter frame" | |
| " * interpreter exit frame" | |
| " * dart frame corresponding to StackFrame.foo" | |
| " * dart frame corresponding to StackFrame2Test.testMain" | |
| " * interpreter entry frame" | |
| " */" | |
| " StackFrame.equals(11, StackFrame.frameCount());" | |
| " StackFrame.equals(5, StackFrame.dartFrameCount());" | |
| " StackFrame.validateFrame(0, \"StackFrame_validateFrame\");" | |
| " StackFrame.validateFrame(1, \"StackFrame2Test_noSuchMethod\");" | |
| " StackFrame.validateFrame(3, \"StackFrame2Test_foo\");" | |
| " StackFrame.validateFrame(4, \"StackFrame2Test_testMain\");" | |
| " return 5;" | |
| " }" | |
| " @pragma('vm:entry-point', 'call')\n" | |
| " 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));" | |
| " }" | |
| "}"; | |
| #if defined(PRODUCT) | |
| return; // FLAG_interpreter is a constant in PRODUCT builds. | |
| #else | |
| SetFlagScope<bool> sfs(&FLAG_interpreter, true); | |
| #endif | |
| 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, nullptr)); | |
| } | |
| #endif | |
| } // namespace dart |