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 36 : void PrintStackTrace(v8::Isolate* isolate, v8::Local<v8::StackTrace> stack) {
36 36 : printf("Stack Trace (length %d):\n", stack->GetFrameCount());
37 168 : for (int i = 0, e = stack->GetFrameCount(); i != e; ++i) {
38 132 : v8::Local<v8::StackFrame> frame = stack->GetFrame(isolate, i);
39 132 : v8::Local<v8::String> script = frame->GetScriptName();
40 132 : v8::Local<v8::String> func = frame->GetFunctionName();
41 396 : printf(
42 : "[%d] (%s) %s:%d:%d\n", i,
43 132 : script.IsEmpty() ? "<null>" : *v8::String::Utf8Value(isolate, script),
44 264 : func.IsEmpty() ? "<null>" : *v8::String::Utf8Value(isolate, func),
45 : frame->GetLineNumber(), frame->GetColumn());
46 : }
47 36 : }
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 36 : 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 36 : CHECK(exc->IsJSError());
60 :
61 : v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(i_isolate);
62 :
63 : // Extract stack frame from the exception.
64 36 : Local<v8::Value> localExc = Utils::ToLocal(exc);
65 36 : v8::Local<v8::StackTrace> stack = v8::Exception::GetStackTrace(localExc);
66 36 : PrintStackTrace(v8_isolate, stack);
67 36 : CHECK(!stack.IsEmpty());
68 36 : CHECK_EQ(N, stack->GetFrameCount());
69 :
70 300 : for (int frameNr = 0; frameNr < N; ++frameNr) {
71 132 : v8::Local<v8::StackFrame> frame = stack->GetFrame(v8_isolate, frameNr);
72 396 : v8::String::Utf8Value funName(v8_isolate, frame->GetFunctionName());
73 132 : CHECK_CSTREQ(excInfos[frameNr].func_name, *funName);
74 : // Line and column are 1-based in v8::StackFrame, just as in ExceptionInfo.
75 132 : CHECK_EQ(excInfos[frameNr].line_nr, frame->GetLineNumber());
76 132 : CHECK_EQ(excInfos[frameNr].column, frame->GetColumn());
77 : }
78 :
79 36 : CheckComputeLocation(i_isolate, exc, excInfos[0]);
80 36 : }
81 :
82 36 : void CheckComputeLocation(v8::internal::Isolate* i_isolate, Handle<Object> exc,
83 : const ExceptionInfo& topLocation) {
84 36 : MessageLocation loc;
85 36 : CHECK(i_isolate->ComputeLocationFromStackTrace(&loc, exc));
86 : printf("loc start: %d, end: %d\n", loc.start_pos(), loc.end_pos());
87 36 : Handle<JSMessageObject> message = i_isolate->CreateMessage(exc, nullptr);
88 108 : printf("msg start: %d, end: %d, line: %d, col: %d\n",
89 : message->start_position(), message->end_position(),
90 : message->GetLineNumber(), message->GetColumnNumber());
91 36 : CHECK_EQ(loc.start_pos(), message->start_position());
92 36 : CHECK_EQ(loc.end_pos(), message->end_position());
93 : // In the message, the line is 1-based, but the column is 0-based.
94 36 : CHECK_EQ(topLocation.line_nr, message->GetLineNumber());
95 36 : CHECK_LE(1, topLocation.column);
96 36 : CHECK_EQ(topLocation.column - 1, message->GetColumnNumber());
97 36 : }
98 :
99 : #undef CHECK_CSTREQ
100 :
101 : } // namespace
102 :
103 : // Call from JS to wasm to JS and throw an Error from JS.
104 26663 : WASM_EXEC_TEST(CollectDetailedWasmStack_ExplicitThrowFromJs) {
105 12 : TestSignatures sigs;
106 : 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 : *v8::Local<v8::Function>::Cast(CompileRun(source))));
112 12 : ManuallyImportedJSFunction import = {sigs.v_v(), js_function};
113 : uint32_t js_throwing_index = 0;
114 24 : WasmRunner<void> r(execution_tier, &import);
115 :
116 : // Add a nop such that we don't always get position 1.
117 12 : BUILD(r, WASM_NOP, WASM_CALL_FUNCTION0(js_throwing_index));
118 12 : uint32_t wasm_index_1 = r.function()->func_index;
119 :
120 12 : WasmFunctionCompiler& f2 = r.NewFunction<void>("call_main");
121 12 : BUILD(f2, WASM_CALL_FUNCTION0(wasm_index_1));
122 : uint32_t wasm_index_2 = f2.function_index();
123 :
124 12 : 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 : CompileRun("(function callFn(fn) { fn(); })"))));
129 :
130 : Isolate* isolate = js_wasm_wrapper->GetIsolate();
131 : isolate->SetCaptureStackTraceForUncaughtExceptions(true, 10,
132 12 : v8::StackTrace::kOverview);
133 24 : Handle<Object> global(isolate->context()->global_object(), isolate);
134 12 : 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 12 : Execution::MessageHandling::kReport, &maybe_exc);
139 12 : 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 12 : {"main", static_cast<int>(wasm_index_1) + 1, 3}, // -
146 12 : {"call_main", static_cast<int>(wasm_index_2) + 1, 2}, // -
147 : {"callFn", 1, 24} // -
148 36 : };
149 : CheckExceptionInfos(isolate, maybe_exc.ToHandleChecked(),
150 12 : expected_exceptions);
151 12 : }
152 :
153 : // Trigger a trap in wasm, stack should be JS -> wasm -> wasm.
154 26655 : WASM_EXEC_TEST(CollectDetailedWasmStack_WasmError) {
155 56 : 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 24 : int unreachable_pos = 1 << (8 * pos_shift);
158 24 : TestSignatures sigs;
159 : // Create a WasmRunner with stack checks and traps enabled.
160 : WasmRunner<int> r(execution_tier, nullptr, "main",
161 48 : kRuntimeExceptionSupport);
162 :
163 24 : std::vector<byte> code(unreachable_pos + 1, kExprNop);
164 48 : code[unreachable_pos] = kExprUnreachable;
165 24 : r.Build(code.data(), code.data() + code.size());
166 :
167 24 : uint32_t wasm_index_1 = r.function()->func_index;
168 :
169 24 : WasmFunctionCompiler& f2 = r.NewFunction<int>("call_main");
170 24 : BUILD(f2, WASM_CALL_FUNCTION0(0));
171 : uint32_t wasm_index_2 = f2.function_index();
172 :
173 24 : 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 : CompileRun("(function callFn(fn) { fn(); })"))));
178 :
179 : Isolate* isolate = js_wasm_wrapper->GetIsolate();
180 : isolate->SetCaptureStackTraceForUncaughtExceptions(
181 24 : true, 10, v8::StackTrace::kOverview);
182 48 : Handle<Object> global(isolate->context()->global_object(), isolate);
183 24 : 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 24 : Execution::MessageHandling::kReport, &maybe_exc);
188 24 : CHECK(maybe_return_obj.is_null());
189 24 : 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 24 : const int expected_main_pos = unreachable_pos + kMainLocalsLength + 1;
194 : ExceptionInfo expected_exceptions[] = {
195 24 : {"main", static_cast<int>(wasm_index_1) + 1, expected_main_pos}, // -
196 24 : {"call_main", static_cast<int>(wasm_index_2) + 1, 2}, // -
197 : {"callFn", 1, 24} //-
198 72 : };
199 24 : CheckExceptionInfos(isolate, exception, expected_exceptions);
200 : }
201 8 : }
202 :
203 : } // namespace test_wasm_stack
204 : } // namespace wasm
205 : } // namespace internal
206 79917 : } // namespace v8
|