Line data Source code
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/api.h"
6 : #include "src/assembler-inl.h"
7 : #include "test/cctest/cctest.h"
8 : #include "test/cctest/compiler/value-helper.h"
9 : #include "test/cctest/wasm/wasm-run-utils.h"
10 : #include "test/common/wasm/test-signatures.h"
11 : #include "test/common/wasm/wasm-macro-gen.h"
12 :
13 : namespace v8 {
14 : namespace internal {
15 : namespace wasm {
16 : namespace test_wasm_stack {
17 :
18 : using v8::Local;
19 : using v8::Utils;
20 :
21 : namespace {
22 :
23 : #define CHECK_CSTREQ(exp, found) \
24 : do { \
25 : const char* exp_ = (exp); \
26 : const char* found_ = (found); \
27 : DCHECK_NOT_NULL(exp); \
28 : if (V8_UNLIKELY(found_ == nullptr || strcmp(exp_, found_) != 0)) { \
29 : V8_Fatal(__FILE__, __LINE__, \
30 : "Check failed: (%s) != (%s) ('%s' vs '%s').", #exp, #found, \
31 : exp_, found_ ? found_ : "<null>"); \
32 : } \
33 : } while (0)
34 :
35 24 : void PrintStackTrace(v8::Isolate* isolate, v8::Local<v8::StackTrace> stack) {
36 24 : printf("Stack Trace (length %d):\n", stack->GetFrameCount());
37 108 : for (int i = 0, e = stack->GetFrameCount(); i != e; ++i) {
38 84 : v8::Local<v8::StackFrame> frame = stack->GetFrame(i);
39 84 : v8::Local<v8::String> script = frame->GetScriptName();
40 84 : v8::Local<v8::String> func = frame->GetFunctionName();
41 : printf(
42 : "[%d] (%s) %s:%d:%d\n", i,
43 84 : script.IsEmpty() ? "<null>" : *v8::String::Utf8Value(isolate, script),
44 252 : func.IsEmpty() ? "<null>" : *v8::String::Utf8Value(isolate, func),
45 252 : frame->GetLineNumber(), frame->GetColumn());
46 : }
47 24 : }
48 :
49 : struct ExceptionInfo {
50 : const char* func_name;
51 : int line_nr; // 1-based
52 : int column; // 1-based
53 : };
54 :
55 : template <int N>
56 24 : void CheckExceptionInfos(v8::internal::Isolate* i_isolate, Handle<Object> exc,
57 : const ExceptionInfo (&excInfos)[N]) {
58 : // Check that it's indeed an Error object.
59 24 : CHECK(exc->IsJSError());
60 :
61 : v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(i_isolate);
62 :
63 : // Extract stack frame from the exception.
64 24 : Local<v8::Value> localExc = Utils::ToLocal(exc);
65 24 : v8::Local<v8::StackTrace> stack = v8::Exception::GetStackTrace(localExc);
66 24 : PrintStackTrace(v8_isolate, stack);
67 24 : CHECK(!stack.IsEmpty());
68 24 : CHECK_EQ(N, stack->GetFrameCount());
69 :
70 84 : for (int frameNr = 0; frameNr < N; ++frameNr) {
71 84 : v8::Local<v8::StackFrame> frame = stack->GetFrame(frameNr);
72 168 : v8::String::Utf8Value funName(v8_isolate, frame->GetFunctionName());
73 84 : CHECK_CSTREQ(excInfos[frameNr].func_name, *funName);
74 : // Line and column are 1-based in v8::StackFrame, just as in ExceptionInfo.
75 84 : CHECK_EQ(excInfos[frameNr].line_nr, frame->GetLineNumber());
76 84 : CHECK_EQ(excInfos[frameNr].column, frame->GetColumn());
77 : }
78 :
79 24 : CheckComputeLocation(i_isolate, exc, excInfos[0]);
80 24 : }
81 :
82 24 : void CheckComputeLocation(v8::internal::Isolate* i_isolate, Handle<Object> exc,
83 : const ExceptionInfo& topLocation) {
84 24 : MessageLocation loc;
85 24 : CHECK(i_isolate->ComputeLocationFromStackTrace(&loc, exc));
86 24 : printf("loc start: %d, end: %d\n", loc.start_pos(), loc.end_pos());
87 24 : Handle<JSMessageObject> message = i_isolate->CreateMessage(exc, nullptr);
88 : printf("msg start: %d, end: %d, line: %d, col: %d\n",
89 : message->start_position(), message->end_position(),
90 48 : message->GetLineNumber(), message->GetColumnNumber());
91 24 : CHECK_EQ(loc.start_pos(), message->start_position());
92 24 : CHECK_EQ(loc.end_pos(), message->end_position());
93 : // In the message, the line is 1-based, but the column is 0-based.
94 24 : CHECK_EQ(topLocation.line_nr, message->GetLineNumber());
95 24 : CHECK_LE(1, topLocation.column);
96 24 : CHECK_EQ(topLocation.column - 1, message->GetColumnNumber());
97 24 : }
98 :
99 : #undef CHECK_CSTREQ
100 :
101 : } // namespace
102 :
103 : // Call from JS to wasm to JS and throw an Error from JS.
104 23724 : TEST(CollectDetailedWasmStack_ExplicitThrowFromJs) {
105 6 : WasmRunner<void> r(kExecuteCompiled);
106 6 : TestSignatures sigs;
107 :
108 : Handle<FixedArray> js_imports_table =
109 6 : r.main_isolate()->factory()->NewFixedArray(2 * 3 + 1, TENURED);
110 : uint32_t js_throwing_index = r.builder().AddJsFunction(
111 : sigs.v_v(),
112 : "(function js() {\n function a() {\n throw new Error(); };\n a(); })",
113 6 : js_imports_table);
114 :
115 : // Add a nop such that we don't always get position 1.
116 6 : BUILD(r, WASM_NOP, WASM_CALL_FUNCTION0(js_throwing_index));
117 6 : uint32_t wasm_index_1 = r.function()->func_index;
118 :
119 12 : WasmFunctionCompiler& f2 = r.NewFunction<void>("call_main");
120 6 : BUILD(f2, WASM_CALL_FUNCTION0(wasm_index_1));
121 : uint32_t wasm_index_2 = f2.function_index();
122 :
123 6 : Handle<JSFunction> js_wasm_wrapper = r.builder().WrapCode(wasm_index_2);
124 :
125 : Handle<JSFunction> js_trampoline = Handle<JSFunction>::cast(
126 : v8::Utils::OpenHandle(*v8::Local<v8::Function>::Cast(
127 : CompileRun("(function callFn(fn) { fn(); })"))));
128 :
129 6 : Isolate* isolate = js_wasm_wrapper->GetIsolate();
130 : isolate->SetCaptureStackTraceForUncaughtExceptions(true, 10,
131 6 : v8::StackTrace::kOverview);
132 6 : Handle<Object> global(isolate->context()->global_object(), isolate);
133 : MaybeHandle<Object> maybe_exc;
134 : Handle<Object> args[] = {js_wasm_wrapper};
135 : MaybeHandle<Object> returnObjMaybe =
136 : Execution::TryCall(isolate, js_trampoline, global, 1, args,
137 6 : Execution::MessageHandling::kReport, &maybe_exc);
138 6 : CHECK(returnObjMaybe.is_null());
139 :
140 : // Line and column are 1-based, so add 1 for the expected wasm output.
141 : ExceptionInfo expected_exceptions[] = {
142 : {"a", 3, 8}, // -
143 : {"js", 4, 2}, // -
144 6 : {"main", static_cast<int>(wasm_index_1) + 1, 3}, // -
145 6 : {"call_main", static_cast<int>(wasm_index_2) + 1, 2}, // -
146 : {"callFn", 1, 24} // -
147 18 : };
148 : CheckExceptionInfos(isolate, maybe_exc.ToHandleChecked(),
149 6 : expected_exceptions);
150 6 : }
151 :
152 : // Trigger a trap in wasm, stack should be JS -> wasm -> wasm.
153 23724 : TEST(CollectDetailedWasmStack_WasmError) {
154 24 : for (int pos_shift = 0; pos_shift < 3; ++pos_shift) {
155 : // Test a position with 1, 2 or 3 bytes needed to represent it.
156 18 : int unreachable_pos = 1 << (8 * pos_shift);
157 18 : TestSignatures sigs;
158 : // Create a WasmRunner with stack checks and traps enabled.
159 : WasmRunner<int> r(kExecuteCompiled, "main",
160 18 : compiler::kRuntimeExceptionSupport);
161 :
162 18 : std::vector<byte> code(unreachable_pos + 1, kExprNop);
163 36 : code[unreachable_pos] = kExprUnreachable;
164 36 : r.Build(code.data(), code.data() + code.size());
165 :
166 18 : uint32_t wasm_index_1 = r.function()->func_index;
167 :
168 36 : WasmFunctionCompiler& f2 = r.NewFunction<int>("call_main");
169 18 : BUILD(f2, WASM_CALL_FUNCTION0(0));
170 : uint32_t wasm_index_2 = f2.function_index();
171 :
172 18 : Handle<JSFunction> js_wasm_wrapper = r.builder().WrapCode(wasm_index_2);
173 :
174 : Handle<JSFunction> js_trampoline = Handle<JSFunction>::cast(
175 : v8::Utils::OpenHandle(*v8::Local<v8::Function>::Cast(
176 : CompileRun("(function callFn(fn) { fn(); })"))));
177 :
178 18 : Isolate* isolate = js_wasm_wrapper->GetIsolate();
179 : isolate->SetCaptureStackTraceForUncaughtExceptions(
180 18 : true, 10, v8::StackTrace::kOverview);
181 18 : Handle<Object> global(isolate->context()->global_object(), isolate);
182 : MaybeHandle<Object> maybe_exc;
183 : Handle<Object> args[] = {js_wasm_wrapper};
184 : MaybeHandle<Object> maybe_return_obj =
185 : Execution::TryCall(isolate, js_trampoline, global, 1, args,
186 18 : Execution::MessageHandling::kReport, &maybe_exc);
187 18 : CHECK(maybe_return_obj.is_null());
188 18 : Handle<Object> exception = maybe_exc.ToHandleChecked();
189 :
190 : static constexpr int kMainLocalsLength = 1;
191 : // Line and column are 1-based, so add 1 for the expected wasm output.
192 18 : const int expected_main_pos = unreachable_pos + kMainLocalsLength + 1;
193 : ExceptionInfo expected_exceptions[] = {
194 18 : {"main", static_cast<int>(wasm_index_1) + 1, expected_main_pos}, // -
195 18 : {"call_main", static_cast<int>(wasm_index_2) + 1, 2}, // -
196 : {"callFn", 1, 24} //-
197 54 : };
198 18 : CheckExceptionInfos(isolate, exception, expected_exceptions);
199 : }
200 6 : }
201 :
202 : } // namespace test_wasm_stack
203 : } // namespace wasm
204 : } // namespace internal
205 71154 : } // namespace v8
|