// 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() == 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(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(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(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(entry->function_); } } return nullptr; } // Unit test case to verify stack frame iteration. TEST_CASE(ValidateStackFrameIteration) { // clang-format off const char* 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 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\");" " }" "}" "class StackFrameTest {" " static testMain() {" " Second obj = new Second();" " obj.method1(1);" " obj.method1(2);" " }" "}"; // clang-format on Dart_Handle lib = TestCase::LoadTestScript( kScriptChars, reinterpret_cast(native_lookup)); Dart_Handle cls = Dart_GetClass(lib, NewString("StackFrameTest")); EXPECT_VALID(Dart_Invoke(cls, NewString("testMain"), 0, nullptr)); } // Unit test case to verify stack frame iteration. TEST_CASE(ValidateNoSuchMethodStackFrameIteration) { const char* const 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));" " }" "}"; Dart_Handle lib = TestCase::LoadTestScript( kScriptChars, reinterpret_cast(native_lookup)); Dart_Handle cls = Dart_GetClass(lib, NewString("StackFrame2Test")); EXPECT_VALID(Dart_Invoke(cls, NewString("testMain"), 0, nullptr)); } } // namespace dart