1// Copyright 2016 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "src/wasm/wasm-macro-gen.h" 6 7#include "test/cctest/cctest.h" 8#include "test/cctest/compiler/value-helper.h" 9#include "test/cctest/wasm/test-signatures.h" 10#include "test/cctest/wasm/wasm-run-utils.h" 11 12using namespace v8::base; 13using namespace v8::internal; 14using namespace v8::internal::compiler; 15using namespace v8::internal::wasm; 16 17using v8::Local; 18using v8::Utils; 19 20namespace { 21 22#define CHECK_CSTREQ(exp, found) \ 23 do { \ 24 const char* exp_ = (exp); \ 25 const char* found_ = (found); \ 26 DCHECK_NOT_NULL(exp); \ 27 if (V8_UNLIKELY(found_ == nullptr || strcmp(exp_, found_) != 0)) { \ 28 V8_Fatal(__FILE__, __LINE__, \ 29 "Check failed: (%s) != (%s) ('%s' vs '%s').", #exp, #found, \ 30 exp_, found_ ? found_ : "<null>"); \ 31 } \ 32 } while (0) 33 34void PrintStackTrace(v8::Local<v8::StackTrace> stack) { 35 printf("Stack Trace (length %d):\n", stack->GetFrameCount()); 36 for (int i = 0, e = stack->GetFrameCount(); i != e; ++i) { 37 v8::Local<v8::StackFrame> frame = stack->GetFrame(i); 38 v8::Local<v8::String> script = frame->GetScriptName(); 39 v8::Local<v8::String> func = frame->GetFunctionName(); 40 printf("[%d] (%s) %s:%d:%d\n", i, 41 script.IsEmpty() ? "<null>" : *v8::String::Utf8Value(script), 42 func.IsEmpty() ? "<null>" : *v8::String::Utf8Value(func), 43 frame->GetLineNumber(), frame->GetColumn()); 44 } 45} 46 47struct ExceptionInfo { 48 const char* func_name; 49 int line_nr; 50 int column; 51}; 52 53template <int N> 54void CheckExceptionInfos(Handle<Object> exc, 55 const ExceptionInfo (&excInfos)[N]) { 56 // Check that it's indeed an Error object. 57 CHECK(exc->IsJSError()); 58 59 // Extract stack frame from the exception. 60 Local<v8::Value> localExc = Utils::ToLocal(exc); 61 v8::Local<v8::StackTrace> stack = v8::Exception::GetStackTrace(localExc); 62 PrintStackTrace(stack); 63 CHECK(!stack.IsEmpty()); 64 CHECK_EQ(N, stack->GetFrameCount()); 65 66 for (int frameNr = 0; frameNr < N; ++frameNr) { 67 v8::Local<v8::StackFrame> frame = stack->GetFrame(frameNr); 68 v8::String::Utf8Value funName(frame->GetFunctionName()); 69 CHECK_CSTREQ(excInfos[frameNr].func_name, *funName); 70 CHECK_EQ(excInfos[frameNr].line_nr, frame->GetLineNumber()); 71 CHECK_EQ(excInfos[frameNr].column, frame->GetColumn()); 72 } 73} 74 75} // namespace 76 77// Call from JS to WASM to JS and throw an Error from JS. 78TEST(CollectDetailedWasmStack_ExplicitThrowFromJs) { 79 TestSignatures sigs; 80 TestingModule module; 81 82 // Initialize WasmFunctionCompiler first, since it sets up the HandleScope. 83 WasmFunctionCompiler comp1(sigs.v_v(), &module); 84 85 uint32_t js_throwing_index = module.AddJsFunction( 86 sigs.v_v(), 87 "(function js() {\n function a() {\n throw new Error(); };\n a(); })"); 88 89 // Add a nop such that we don't always get position 1. 90 BUILD(comp1, WASM_NOP, WASM_CALL_FUNCTION0(js_throwing_index)); 91 uint32_t wasm_index = comp1.CompileAndAdd(); 92 93 WasmFunctionCompiler comp2(sigs.v_v(), &module); 94 BUILD(comp2, WASM_CALL_FUNCTION0(wasm_index)); 95 uint32_t wasm_index_2 = comp2.CompileAndAdd(); 96 97 Handle<JSFunction> js_wasm_wrapper = module.WrapCode(wasm_index_2); 98 99 Handle<JSFunction> js_trampoline = Handle<JSFunction>::cast( 100 v8::Utils::OpenHandle(*v8::Local<v8::Function>::Cast( 101 CompileRun("(function callFn(fn) { fn(); })")))); 102 103 Isolate* isolate = js_wasm_wrapper->GetIsolate(); 104 isolate->SetCaptureStackTraceForUncaughtExceptions(true, 10, 105 v8::StackTrace::kOverview); 106 Handle<Object> global(isolate->context()->global_object(), isolate); 107 MaybeHandle<Object> maybe_exc; 108 Handle<Object> args[] = {js_wasm_wrapper}; 109 MaybeHandle<Object> returnObjMaybe = 110 Execution::TryCall(isolate, js_trampoline, global, 1, args, &maybe_exc); 111 CHECK(returnObjMaybe.is_null()); 112 113 // The column is 1-based, so add 1 to the actual byte offset. 114 ExceptionInfo expected_exceptions[] = { 115 {"a", 3, 8}, // - 116 {"js", 4, 2}, // - 117 {"<WASM UNNAMED>", static_cast<int>(wasm_index), 3}, // - 118 {"<WASM UNNAMED>", static_cast<int>(wasm_index_2), 2}, // - 119 {"callFn", 1, 24} // - 120 }; 121 CheckExceptionInfos(maybe_exc.ToHandleChecked(), expected_exceptions); 122} 123 124// Trigger a trap in WASM, stack should be JS -> WASM -> WASM. 125TEST(CollectDetailedWasmStack_WasmError) { 126 TestSignatures sigs; 127 TestingModule module; 128 129 WasmFunctionCompiler comp1(sigs.i_v(), &module, 130 ArrayVector("exec_unreachable")); 131 // Set the execution context, such that a runtime error can be thrown. 132 comp1.SetModuleContext(); 133 BUILD(comp1, WASM_UNREACHABLE); 134 uint32_t wasm_index = comp1.CompileAndAdd(); 135 136 WasmFunctionCompiler comp2(sigs.i_v(), &module, 137 ArrayVector("call_exec_unreachable")); 138 BUILD(comp2, WASM_CALL_FUNCTION0(wasm_index)); 139 uint32_t wasm_index_2 = comp2.CompileAndAdd(); 140 141 Handle<JSFunction> js_wasm_wrapper = module.WrapCode(wasm_index_2); 142 143 Handle<JSFunction> js_trampoline = Handle<JSFunction>::cast( 144 v8::Utils::OpenHandle(*v8::Local<v8::Function>::Cast( 145 CompileRun("(function callFn(fn) { fn(); })")))); 146 147 Isolate* isolate = js_wasm_wrapper->GetIsolate(); 148 isolate->SetCaptureStackTraceForUncaughtExceptions(true, 10, 149 v8::StackTrace::kOverview); 150 Handle<Object> global(isolate->context()->global_object(), isolate); 151 MaybeHandle<Object> maybe_exc; 152 Handle<Object> args[] = {js_wasm_wrapper}; 153 MaybeHandle<Object> maybe_return_obj = 154 Execution::TryCall(isolate, js_trampoline, global, 1, args, &maybe_exc); 155 CHECK(maybe_return_obj.is_null()); 156 157 // The column is 1-based, so add 1 to the actual byte offset. 158 ExceptionInfo expected_exceptions[] = { 159 {"<WASM UNNAMED>", static_cast<int>(wasm_index), 2}, // - 160 {"<WASM UNNAMED>", static_cast<int>(wasm_index_2), 2}, // - 161 {"callFn", 1, 24} //- 162 }; 163 CheckExceptionInfos(maybe_exc.ToHandleChecked(), expected_exceptions); 164} 165