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