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-inl.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 (false)
34 :
35 60 : void PrintStackTrace(v8::Isolate* isolate, v8::Local<v8::StackTrace> stack) {
36 60 : printf("Stack Trace (length %d):\n", stack->GetFrameCount());
37 270 : for (int i = 0, e = stack->GetFrameCount(); i != e; ++i) {
38 210 : v8::Local<v8::StackFrame> frame = stack->GetFrame(isolate, i);
39 210 : v8::Local<v8::String> script = frame->GetScriptName();
40 210 : v8::Local<v8::String> func = frame->GetFunctionName();
41 : printf(
42 : "[%d] (%s) %s:%d:%d\n", i,
43 210 : script.IsEmpty() ? "<null>" : *v8::String::Utf8Value(isolate, script),
44 630 : func.IsEmpty() ? "<null>" : *v8::String::Utf8Value(isolate, func),
45 630 : frame->GetLineNumber(), frame->GetColumn());
46 : }
47 60 : }
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 60 : 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 120 : CHECK(exc->IsJSError());
60 :
61 : v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(i_isolate);
62 :
63 : // Extract stack frame from the exception.
64 60 : Local<v8::Value> localExc = Utils::ToLocal(exc);
65 60 : v8::Local<v8::StackTrace> stack = v8::Exception::GetStackTrace(localExc);
66 60 : PrintStackTrace(v8_isolate, stack);
67 60 : CHECK(!stack.IsEmpty());
68 60 : CHECK_EQ(N, stack->GetFrameCount());
69 :
70 210 : for (int frameNr = 0; frameNr < N; ++frameNr) {
71 210 : v8::Local<v8::StackFrame> frame = stack->GetFrame(v8_isolate, frameNr);
72 420 : v8::String::Utf8Value funName(v8_isolate, frame->GetFunctionName());
73 210 : CHECK_CSTREQ(excInfos[frameNr].func_name, *funName);
74 : // Line and column are 1-based in v8::StackFrame, just as in ExceptionInfo.
75 210 : CHECK_EQ(excInfos[frameNr].line_nr, frame->GetLineNumber());
76 210 : CHECK_EQ(excInfos[frameNr].column, frame->GetColumn());
77 : }
78 :
79 60 : CheckComputeLocation(i_isolate, exc, excInfos[0]);
80 60 : }
81 :
82 60 : void CheckComputeLocation(v8::internal::Isolate* i_isolate, Handle<Object> exc,
83 : const ExceptionInfo& topLocation) {
84 60 : MessageLocation loc;
85 60 : CHECK(i_isolate->ComputeLocationFromStackTrace(&loc, exc));
86 60 : printf("loc start: %d, end: %d\n", loc.start_pos(), loc.end_pos());
87 60 : 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 180 : message->GetLineNumber(), message->GetColumnNumber());
91 60 : CHECK_EQ(loc.start_pos(), message->start_position());
92 60 : CHECK_EQ(loc.end_pos(), message->end_position());
93 : // In the message, the line is 1-based, but the column is 0-based.
94 60 : CHECK_EQ(topLocation.line_nr, message->GetLineNumber());
95 60 : CHECK_LE(1, topLocation.column);
96 60 : CHECK_EQ(topLocation.column - 1, message->GetColumnNumber());
97 60 : }
98 :
99 : #undef CHECK_CSTREQ
100 :
101 : } // namespace
102 :
103 : // Call from JS to wasm to JS and throw an Error from JS.
104 28367 : WASM_EXEC_TEST(CollectDetailedWasmStack_ExplicitThrowFromJs) {
105 15 : TestSignatures sigs;
106 15 : HandleScope scope(CcTest::InitIsolateOnce());
107 : const char* source =
108 : "(function js() {\n function a() {\n throw new Error(); };\n a(); })";
109 : Handle<JSFunction> js_function =
110 : Handle<JSFunction>::cast(v8::Utils::OpenHandle(
111 15 : *v8::Local<v8::Function>::Cast(CompileRun(source))));
112 15 : ManuallyImportedJSFunction import = {sigs.v_v(), js_function};
113 : uint32_t js_throwing_index = 0;
114 15 : WasmRunner<void> r(execution_tier, &import);
115 :
116 : // Add a nop such that we don't always get position 1.
117 15 : BUILD(r, WASM_NOP, WASM_CALL_FUNCTION0(js_throwing_index));
118 15 : uint32_t wasm_index_1 = r.function()->func_index;
119 :
120 30 : WasmFunctionCompiler& f2 = r.NewFunction<void>("call_main");
121 15 : BUILD(f2, WASM_CALL_FUNCTION0(wasm_index_1));
122 : uint32_t wasm_index_2 = f2.function_index();
123 :
124 15 : Handle<JSFunction> js_wasm_wrapper = r.builder().WrapCode(wasm_index_2);
125 :
126 : Handle<JSFunction> js_trampoline = Handle<JSFunction>::cast(
127 : v8::Utils::OpenHandle(*v8::Local<v8::Function>::Cast(
128 15 : CompileRun("(function callFn(fn) { fn(); })"))));
129 :
130 : Isolate* isolate = js_wasm_wrapper->GetIsolate();
131 : isolate->SetCaptureStackTraceForUncaughtExceptions(true, 10,
132 15 : v8::StackTrace::kOverview);
133 30 : Handle<Object> global(isolate->context()->global_object(), isolate);
134 15 : MaybeHandle<Object> maybe_exc;
135 : Handle<Object> args[] = {js_wasm_wrapper};
136 : MaybeHandle<Object> returnObjMaybe =
137 : Execution::TryCall(isolate, js_trampoline, global, 1, args,
138 15 : Execution::MessageHandling::kReport, &maybe_exc);
139 15 : CHECK(returnObjMaybe.is_null());
140 :
141 : // Line and column are 1-based, so add 1 for the expected wasm output.
142 : ExceptionInfo expected_exceptions[] = {
143 : {"a", 3, 8}, // -
144 : {"js", 4, 2}, // -
145 15 : {"main", static_cast<int>(wasm_index_1) + 1, 3}, // -
146 15 : {"call_main", static_cast<int>(wasm_index_2) + 1, 2}, // -
147 : {"callFn", 1, 24} // -
148 45 : };
149 : CheckExceptionInfos(isolate, maybe_exc.ToHandleChecked(),
150 15 : expected_exceptions);
151 15 : }
152 :
153 : // Trigger a trap in wasm, stack should be JS -> wasm -> wasm.
154 28367 : WASM_EXEC_TEST(CollectDetailedWasmStack_WasmError) {
155 60 : for (int pos_shift = 0; pos_shift < 3; ++pos_shift) {
156 : // Test a position with 1, 2 or 3 bytes needed to represent it.
157 45 : int unreachable_pos = 1 << (8 * pos_shift);
158 45 : TestSignatures sigs;
159 : // Create a WasmRunner with stack checks and traps enabled.
160 : WasmRunner<int> r(execution_tier, nullptr, "main",
161 45 : kRuntimeExceptionSupport);
162 :
163 45 : std::vector<byte> code(unreachable_pos + 1, kExprNop);
164 90 : code[unreachable_pos] = kExprUnreachable;
165 90 : r.Build(code.data(), code.data() + code.size());
166 :
167 45 : uint32_t wasm_index_1 = r.function()->func_index;
168 :
169 90 : WasmFunctionCompiler& f2 = r.NewFunction<int>("call_main");
170 45 : BUILD(f2, WASM_CALL_FUNCTION0(0));
171 : uint32_t wasm_index_2 = f2.function_index();
172 :
173 45 : Handle<JSFunction> js_wasm_wrapper = r.builder().WrapCode(wasm_index_2);
174 :
175 : Handle<JSFunction> js_trampoline = Handle<JSFunction>::cast(
176 : v8::Utils::OpenHandle(*v8::Local<v8::Function>::Cast(
177 45 : CompileRun("(function callFn(fn) { fn(); })"))));
178 :
179 : Isolate* isolate = js_wasm_wrapper->GetIsolate();
180 : isolate->SetCaptureStackTraceForUncaughtExceptions(
181 45 : true, 10, v8::StackTrace::kOverview);
182 90 : Handle<Object> global(isolate->context()->global_object(), isolate);
183 45 : MaybeHandle<Object> maybe_exc;
184 : Handle<Object> args[] = {js_wasm_wrapper};
185 : MaybeHandle<Object> maybe_return_obj =
186 : Execution::TryCall(isolate, js_trampoline, global, 1, args,
187 45 : Execution::MessageHandling::kReport, &maybe_exc);
188 45 : CHECK(maybe_return_obj.is_null());
189 45 : Handle<Object> exception = maybe_exc.ToHandleChecked();
190 :
191 : static constexpr int kMainLocalsLength = 1;
192 : // Line and column are 1-based, so add 1 for the expected wasm output.
193 45 : const int expected_main_pos = unreachable_pos + kMainLocalsLength + 1;
194 : ExceptionInfo expected_exceptions[] = {
195 45 : {"main", static_cast<int>(wasm_index_1) + 1, expected_main_pos}, // -
196 45 : {"call_main", static_cast<int>(wasm_index_2) + 1, 2}, // -
197 : {"callFn", 1, 24} //-
198 135 : };
199 45 : CheckExceptionInfos(isolate, exception, expected_exceptions);
200 : }
201 15 : }
202 :
203 : } // namespace test_wasm_stack
204 : } // namespace wasm
205 : } // namespace internal
206 85011 : } // namespace v8
|