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 #if defined(V8_OS_STARBOARD)
6 #include "starboard/system.h"
7 #define __builtin_abort SbSystemBreakIntoDebugger
8 #endif
9
10 #include "src/inspector/v8-stack-trace-impl.h"
11
12 #include <algorithm>
13
14 #include "../../third_party/inspector_protocol/crdtp/json.h"
15 #include "src/debug/debug-interface.h"
16 #include "src/inspector/v8-debugger.h"
17 #include "src/inspector/v8-inspector-impl.h"
18 #include "src/tracing/trace-event.h"
19
20 using v8_crdtp::SpanFrom;
21 using v8_crdtp::json::ConvertCBORToJSON;
22 using v8_crdtp::json::ConvertJSONToCBOR;
23
24 namespace v8_inspector {
25 namespace {
26
27 static const char kId[] = "id";
28 static const char kDebuggerId[] = "debuggerId";
29 static const char kShouldPause[] = "shouldPause";
30
31 static const v8::StackTrace::StackTraceOptions stackTraceOptions =
32 static_cast<v8::StackTrace::StackTraceOptions>(
33 v8::StackTrace::kDetailed |
34 v8::StackTrace::kExposeFramesAcrossSecurityOrigins);
35
toFramesVector(V8Debugger * debugger,v8::Local<v8::StackTrace> v8StackTrace,int maxStackSize)36 std::vector<std::shared_ptr<StackFrame>> toFramesVector(
37 V8Debugger* debugger, v8::Local<v8::StackTrace> v8StackTrace,
38 int maxStackSize) {
39 DCHECK(debugger->isolate()->InContext());
40 int frameCount = std::min(v8StackTrace->GetFrameCount(), maxStackSize);
41
42 TRACE_EVENT1(
43 TRACE_DISABLED_BY_DEFAULT("v8.inspector") "," TRACE_DISABLED_BY_DEFAULT(
44 "v8.stack_trace"),
45 "toFramesVector", "frameCount", frameCount);
46
47 std::vector<std::shared_ptr<StackFrame>> frames(frameCount);
48 for (int i = 0; i < frameCount; ++i) {
49 frames[i] =
50 debugger->symbolize(v8StackTrace->GetFrame(debugger->isolate(), i));
51 }
52 return frames;
53 }
54
calculateAsyncChain(V8Debugger * debugger,std::shared_ptr<AsyncStackTrace> * asyncParent,V8StackTraceId * externalParent,int * maxAsyncDepth)55 void calculateAsyncChain(V8Debugger* debugger,
56 std::shared_ptr<AsyncStackTrace>* asyncParent,
57 V8StackTraceId* externalParent, int* maxAsyncDepth) {
58 *asyncParent = debugger->currentAsyncParent();
59 *externalParent = debugger->currentExternalParent();
60 DCHECK(externalParent->IsInvalid() || !*asyncParent);
61 if (maxAsyncDepth) *maxAsyncDepth = debugger->maxAsyncCallChainDepth();
62
63 // Only the top stack in the chain may be empty, so ensure that second stack
64 // is non-empty (it's the top of appended chain).
65 if (*asyncParent && (*asyncParent)->isEmpty()) {
66 *asyncParent = (*asyncParent)->parent().lock();
67 }
68 }
69
buildInspectorObjectCommon(V8Debugger * debugger,const std::vector<std::shared_ptr<StackFrame>> & frames,const String16 & description,const std::shared_ptr<AsyncStackTrace> & asyncParent,const V8StackTraceId & externalParent,int maxAsyncDepth)70 std::unique_ptr<protocol::Runtime::StackTrace> buildInspectorObjectCommon(
71 V8Debugger* debugger,
72 const std::vector<std::shared_ptr<StackFrame>>& frames,
73 const String16& description,
74 const std::shared_ptr<AsyncStackTrace>& asyncParent,
75 const V8StackTraceId& externalParent, int maxAsyncDepth) {
76 if (asyncParent && frames.empty() &&
77 description == asyncParent->description()) {
78 return asyncParent->buildInspectorObject(debugger, maxAsyncDepth);
79 }
80
81 auto inspectorFrames =
82 std::make_unique<protocol::Array<protocol::Runtime::CallFrame>>();
83 for (const std::shared_ptr<StackFrame>& frame : frames) {
84 V8InspectorClient* client = nullptr;
85 if (debugger && debugger->inspector())
86 client = debugger->inspector()->client();
87 inspectorFrames->emplace_back(frame->buildInspectorObject(client));
88 }
89 std::unique_ptr<protocol::Runtime::StackTrace> stackTrace =
90 protocol::Runtime::StackTrace::create()
91 .setCallFrames(std::move(inspectorFrames))
92 .build();
93 if (!description.isEmpty()) stackTrace->setDescription(description);
94 if (asyncParent) {
95 if (maxAsyncDepth > 0) {
96 stackTrace->setParent(
97 asyncParent->buildInspectorObject(debugger, maxAsyncDepth - 1));
98 } else if (debugger) {
99 stackTrace->setParentId(
100 protocol::Runtime::StackTraceId::create()
101 .setId(stackTraceIdToString(
102 AsyncStackTrace::store(debugger, asyncParent)))
103 .build());
104 }
105 }
106 if (!externalParent.IsInvalid()) {
107 stackTrace->setParentId(
108 protocol::Runtime::StackTraceId::create()
109 .setId(stackTraceIdToString(externalParent.id))
110 .setDebuggerId(
111 internal::V8DebuggerId(externalParent.debugger_id).toString())
112 .build());
113 }
114 return stackTrace;
115 }
116
117 } // namespace
118
V8StackTraceId()119 V8StackTraceId::V8StackTraceId()
120 : id(0), debugger_id(internal::V8DebuggerId().pair()) {}
121
V8StackTraceId(uintptr_t id,const std::pair<int64_t,int64_t> debugger_id)122 V8StackTraceId::V8StackTraceId(uintptr_t id,
123 const std::pair<int64_t, int64_t> debugger_id)
124 : id(id), debugger_id(debugger_id) {}
125
V8StackTraceId(uintptr_t id,const std::pair<int64_t,int64_t> debugger_id,bool should_pause)126 V8StackTraceId::V8StackTraceId(uintptr_t id,
127 const std::pair<int64_t, int64_t> debugger_id,
128 bool should_pause)
129 : id(id), debugger_id(debugger_id), should_pause(should_pause) {}
130
V8StackTraceId(StringView json)131 V8StackTraceId::V8StackTraceId(StringView json)
132 : id(0), debugger_id(internal::V8DebuggerId().pair()) {
133 if (json.length() == 0) return;
134 std::vector<uint8_t> cbor;
135 if (json.is8Bit()) {
136 ConvertJSONToCBOR(
137 v8_crdtp::span<uint8_t>(json.characters8(), json.length()), &cbor);
138 } else {
139 ConvertJSONToCBOR(
140 v8_crdtp::span<uint16_t>(json.characters16(), json.length()), &cbor);
141 }
142 auto dict = protocol::DictionaryValue::cast(
143 protocol::Value::parseBinary(cbor.data(), cbor.size()));
144 if (!dict) return;
145 String16 s;
146 if (!dict->getString(kId, &s)) return;
147 bool isOk = false;
148 int64_t parsedId = s.toInteger64(&isOk);
149 if (!isOk || !parsedId) return;
150 if (!dict->getString(kDebuggerId, &s)) return;
151 internal::V8DebuggerId debuggerId(s);
152 if (!debuggerId.isValid()) return;
153 if (!dict->getBoolean(kShouldPause, &should_pause)) return;
154 id = parsedId;
155 debugger_id = debuggerId.pair();
156 }
157
IsInvalid() const158 bool V8StackTraceId::IsInvalid() const { return !id; }
159
ToString()160 std::unique_ptr<StringBuffer> V8StackTraceId::ToString() {
161 if (IsInvalid()) return nullptr;
162 auto dict = protocol::DictionaryValue::create();
163 dict->setString(kId, String16::fromInteger64(id));
164 dict->setString(kDebuggerId, internal::V8DebuggerId(debugger_id).toString());
165 dict->setBoolean(kShouldPause, should_pause);
166 std::vector<uint8_t> json;
167 v8_crdtp::json::ConvertCBORToJSON(v8_crdtp::SpanFrom(dict->Serialize()),
168 &json);
169 return StringBufferFrom(std::move(json));
170 }
171
StackFrame(String16 && functionName,int scriptId,String16 && sourceURL,int lineNumber,int columnNumber,bool hasSourceURLComment)172 StackFrame::StackFrame(String16&& functionName, int scriptId,
173 String16&& sourceURL, int lineNumber, int columnNumber,
174 bool hasSourceURLComment)
175 : m_functionName(std::move(functionName)),
176 m_scriptId(scriptId),
177 m_sourceURL(std::move(sourceURL)),
178 m_lineNumber(lineNumber),
179 m_columnNumber(columnNumber),
180 m_hasSourceURLComment(hasSourceURLComment) {
181 DCHECK_NE(v8::Message::kNoLineNumberInfo, m_lineNumber + 1);
182 DCHECK_NE(v8::Message::kNoColumnInfo, m_columnNumber + 1);
183 }
184
functionName() const185 const String16& StackFrame::functionName() const { return m_functionName; }
186
scriptId() const187 int StackFrame::scriptId() const { return m_scriptId; }
188
sourceURL() const189 const String16& StackFrame::sourceURL() const { return m_sourceURL; }
190
lineNumber() const191 int StackFrame::lineNumber() const { return m_lineNumber; }
192
columnNumber() const193 int StackFrame::columnNumber() const { return m_columnNumber; }
194
buildInspectorObject(V8InspectorClient * client) const195 std::unique_ptr<protocol::Runtime::CallFrame> StackFrame::buildInspectorObject(
196 V8InspectorClient* client) const {
197 String16 frameUrl;
198 const char* dataURIPrefix = "data:";
199 if (m_sourceURL.substring(0, strlen(dataURIPrefix)) != dataURIPrefix) {
200 frameUrl = m_sourceURL;
201 }
202
203 if (client && !m_hasSourceURLComment && frameUrl.length() > 0) {
204 std::unique_ptr<StringBuffer> url =
205 client->resourceNameToUrl(toStringView(m_sourceURL));
206 if (url) {
207 frameUrl = toString16(url->string());
208 }
209 }
210 return protocol::Runtime::CallFrame::create()
211 .setFunctionName(m_functionName)
212 .setScriptId(String16::fromInteger(m_scriptId))
213 .setUrl(frameUrl)
214 .setLineNumber(m_lineNumber)
215 .setColumnNumber(m_columnNumber)
216 .build();
217 }
218
isEqual(StackFrame * frame) const219 bool StackFrame::isEqual(StackFrame* frame) const {
220 return m_scriptId == frame->m_scriptId &&
221 m_lineNumber == frame->m_lineNumber &&
222 m_columnNumber == frame->m_columnNumber;
223 }
224
225 // static
create(V8Debugger * debugger,v8::Local<v8::StackTrace> v8StackTrace,int maxStackSize)226 std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::create(
227 V8Debugger* debugger, v8::Local<v8::StackTrace> v8StackTrace,
228 int maxStackSize) {
229 DCHECK(debugger);
230
231 v8::Isolate* isolate = debugger->isolate();
232 v8::HandleScope scope(isolate);
233
234 std::vector<std::shared_ptr<StackFrame>> frames;
235 if (!v8StackTrace.IsEmpty() && v8StackTrace->GetFrameCount()) {
236 frames = toFramesVector(debugger, v8StackTrace, maxStackSize);
237 }
238
239 int maxAsyncDepth = 0;
240 std::shared_ptr<AsyncStackTrace> asyncParent;
241 V8StackTraceId externalParent;
242 calculateAsyncChain(debugger, &asyncParent, &externalParent, &maxAsyncDepth);
243 if (frames.empty() && !asyncParent && externalParent.IsInvalid())
244 return nullptr;
245 return std::unique_ptr<V8StackTraceImpl>(new V8StackTraceImpl(
246 std::move(frames), maxAsyncDepth, asyncParent, externalParent));
247 }
248
249 // static
capture(V8Debugger * debugger,int maxStackSize)250 std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::capture(
251 V8Debugger* debugger, int maxStackSize) {
252 DCHECK(debugger);
253
254 TRACE_EVENT1(
255 TRACE_DISABLED_BY_DEFAULT("v8.inspector") "," TRACE_DISABLED_BY_DEFAULT(
256 "v8.stack_trace"),
257 "V8StackTraceImpl::capture", "maxFrameCount", maxStackSize);
258
259 v8::Isolate* isolate = debugger->isolate();
260 v8::HandleScope handleScope(isolate);
261 v8::Local<v8::StackTrace> v8StackTrace;
262 if (isolate->InContext()) {
263 v8StackTrace = v8::StackTrace::CurrentStackTrace(isolate, maxStackSize,
264 stackTraceOptions);
265 }
266 return V8StackTraceImpl::create(debugger, v8StackTrace, maxStackSize);
267 }
268
V8StackTraceImpl(std::vector<std::shared_ptr<StackFrame>> frames,int maxAsyncDepth,std::shared_ptr<AsyncStackTrace> asyncParent,const V8StackTraceId & externalParent)269 V8StackTraceImpl::V8StackTraceImpl(
270 std::vector<std::shared_ptr<StackFrame>> frames, int maxAsyncDepth,
271 std::shared_ptr<AsyncStackTrace> asyncParent,
272 const V8StackTraceId& externalParent)
273 : m_frames(std::move(frames)),
274 m_maxAsyncDepth(maxAsyncDepth),
275 m_asyncParent(std::move(asyncParent)),
276 m_externalParent(externalParent) {}
277
278 V8StackTraceImpl::~V8StackTraceImpl() = default;
279
clone()280 std::unique_ptr<V8StackTrace> V8StackTraceImpl::clone() {
281 return std::unique_ptr<V8StackTrace>(new V8StackTraceImpl(
282 m_frames, 0, std::shared_ptr<AsyncStackTrace>(), V8StackTraceId()));
283 }
284
firstNonEmptySourceURL() const285 StringView V8StackTraceImpl::firstNonEmptySourceURL() const {
286 StackFrameIterator current(this);
287 while (!current.done()) {
288 if (current.frame()->sourceURL().length()) {
289 return toStringView(current.frame()->sourceURL());
290 }
291 current.next();
292 }
293 return StringView();
294 }
295
isEmpty() const296 bool V8StackTraceImpl::isEmpty() const { return m_frames.empty(); }
297
topSourceURL() const298 StringView V8StackTraceImpl::topSourceURL() const {
299 return toStringView(m_frames[0]->sourceURL());
300 }
301
topLineNumber() const302 int V8StackTraceImpl::topLineNumber() const {
303 return m_frames[0]->lineNumber() + 1;
304 }
305
topColumnNumber() const306 int V8StackTraceImpl::topColumnNumber() const {
307 return m_frames[0]->columnNumber() + 1;
308 }
309
topScriptId() const310 int V8StackTraceImpl::topScriptId() const { return m_frames[0]->scriptId(); }
311
topFunctionName() const312 StringView V8StackTraceImpl::topFunctionName() const {
313 return toStringView(m_frames[0]->functionName());
314 }
315
316 std::unique_ptr<protocol::Runtime::StackTrace>
buildInspectorObjectImpl(V8Debugger * debugger) const317 V8StackTraceImpl::buildInspectorObjectImpl(V8Debugger* debugger) const {
318 return buildInspectorObjectImpl(debugger, m_maxAsyncDepth);
319 }
320
321 std::unique_ptr<protocol::Runtime::StackTrace>
buildInspectorObjectImpl(V8Debugger * debugger,int maxAsyncDepth) const322 V8StackTraceImpl::buildInspectorObjectImpl(V8Debugger* debugger,
323 int maxAsyncDepth) const {
324 return buildInspectorObjectCommon(debugger, m_frames, String16(),
325 m_asyncParent.lock(), m_externalParent,
326 maxAsyncDepth);
327 }
328
329 std::unique_ptr<protocol::Runtime::API::StackTrace>
buildInspectorObject(int maxAsyncDepth) const330 V8StackTraceImpl::buildInspectorObject(int maxAsyncDepth) const {
331 return buildInspectorObjectImpl(nullptr,
332 std::min(maxAsyncDepth, m_maxAsyncDepth));
333 }
334
toString() const335 std::unique_ptr<StringBuffer> V8StackTraceImpl::toString() const {
336 String16Builder stackTrace;
337 for (size_t i = 0; i < m_frames.size(); ++i) {
338 const StackFrame& frame = *m_frames[i];
339 stackTrace.append("\n at " + (frame.functionName().length()
340 ? frame.functionName()
341 : "(anonymous function)"));
342 stackTrace.append(" (");
343 stackTrace.append(frame.sourceURL());
344 stackTrace.append(':');
345 stackTrace.append(String16::fromInteger(frame.lineNumber() + 1));
346 stackTrace.append(':');
347 stackTrace.append(String16::fromInteger(frame.columnNumber() + 1));
348 stackTrace.append(')');
349 }
350 return StringBufferFrom(stackTrace.toString());
351 }
352
isEqualIgnoringTopFrame(V8StackTraceImpl * stackTrace) const353 bool V8StackTraceImpl::isEqualIgnoringTopFrame(
354 V8StackTraceImpl* stackTrace) const {
355 StackFrameIterator current(this);
356 StackFrameIterator target(stackTrace);
357
358 current.next();
359 target.next();
360 while (!current.done() && !target.done()) {
361 if (!current.frame()->isEqual(target.frame())) {
362 return false;
363 }
364 current.next();
365 target.next();
366 }
367 return current.done() == target.done();
368 }
369
StackFrameIterator(const V8StackTraceImpl * stackTrace)370 V8StackTraceImpl::StackFrameIterator::StackFrameIterator(
371 const V8StackTraceImpl* stackTrace)
372 : m_currentIt(stackTrace->m_frames.begin()),
373 m_currentEnd(stackTrace->m_frames.end()),
374 m_parent(stackTrace->m_asyncParent.lock().get()) {}
375
next()376 void V8StackTraceImpl::StackFrameIterator::next() {
377 if (m_currentIt == m_currentEnd) return;
378 ++m_currentIt;
379 while (m_currentIt == m_currentEnd && m_parent) {
380 const std::vector<std::shared_ptr<StackFrame>>& frames = m_parent->frames();
381 m_currentIt = frames.begin();
382 m_currentEnd = frames.end();
383 m_parent = m_parent->parent().lock().get();
384 }
385 }
386
done()387 bool V8StackTraceImpl::StackFrameIterator::done() {
388 return m_currentIt == m_currentEnd;
389 }
390
frame()391 StackFrame* V8StackTraceImpl::StackFrameIterator::frame() {
392 return m_currentIt->get();
393 }
394
395 // static
capture(V8Debugger * debugger,const String16 & description,bool skipTopFrame)396 std::shared_ptr<AsyncStackTrace> AsyncStackTrace::capture(
397 V8Debugger* debugger, const String16& description, bool skipTopFrame) {
398 DCHECK(debugger);
399
400 int maxStackSize = debugger->maxCallStackSizeToCapture();
401 TRACE_EVENT1(
402 TRACE_DISABLED_BY_DEFAULT("v8.inspector") "," TRACE_DISABLED_BY_DEFAULT(
403 "v8.stack_trace"),
404 "AsyncStackTrace::capture", "maxFrameCount", maxStackSize);
405
406 v8::Isolate* isolate = debugger->isolate();
407 v8::HandleScope handleScope(isolate);
408
409 std::vector<std::shared_ptr<StackFrame>> frames;
410 if (isolate->InContext()) {
411 v8::Local<v8::StackTrace> v8StackTrace = v8::StackTrace::CurrentStackTrace(
412 isolate, maxStackSize, stackTraceOptions);
413 frames = toFramesVector(debugger, v8StackTrace, maxStackSize);
414 if (skipTopFrame && !frames.empty()) {
415 frames.erase(frames.begin());
416 }
417 }
418
419 std::shared_ptr<AsyncStackTrace> asyncParent;
420 V8StackTraceId externalParent;
421 calculateAsyncChain(debugger, &asyncParent, &externalParent, nullptr);
422
423 if (frames.empty() && !asyncParent && externalParent.IsInvalid())
424 return nullptr;
425
426 // When async call chain is empty but doesn't contain useful schedule stack
427 // but doesn't synchronous we can merge them together. e.g. Promise
428 // ThenableJob.
429 if (asyncParent && frames.empty() &&
430 (asyncParent->m_description == description || description.isEmpty())) {
431 return asyncParent;
432 }
433
434 return std::shared_ptr<AsyncStackTrace>(new AsyncStackTrace(
435 description, std::move(frames), asyncParent, externalParent));
436 }
437
AsyncStackTrace(const String16 & description,std::vector<std::shared_ptr<StackFrame>> frames,std::shared_ptr<AsyncStackTrace> asyncParent,const V8StackTraceId & externalParent)438 AsyncStackTrace::AsyncStackTrace(
439 const String16& description,
440 std::vector<std::shared_ptr<StackFrame>> frames,
441 std::shared_ptr<AsyncStackTrace> asyncParent,
442 const V8StackTraceId& externalParent)
443 : m_id(0),
444 m_description(description),
445 m_frames(std::move(frames)),
446 m_asyncParent(std::move(asyncParent)),
447 m_externalParent(externalParent) {}
448
449 std::unique_ptr<protocol::Runtime::StackTrace>
buildInspectorObject(V8Debugger * debugger,int maxAsyncDepth) const450 AsyncStackTrace::buildInspectorObject(V8Debugger* debugger,
451 int maxAsyncDepth) const {
452 return buildInspectorObjectCommon(debugger, m_frames, m_description,
453 m_asyncParent.lock(), m_externalParent,
454 maxAsyncDepth);
455 }
456
store(V8Debugger * debugger,std::shared_ptr<AsyncStackTrace> stack)457 uintptr_t AsyncStackTrace::store(V8Debugger* debugger,
458 std::shared_ptr<AsyncStackTrace> stack) {
459 if (stack->m_id) return stack->m_id;
460 stack->m_id = debugger->storeStackTrace(stack);
461 return stack->m_id;
462 }
463
description() const464 const String16& AsyncStackTrace::description() const { return m_description; }
465
parent() const466 std::weak_ptr<AsyncStackTrace> AsyncStackTrace::parent() const {
467 return m_asyncParent;
468 }
469
isEmpty() const470 bool AsyncStackTrace::isEmpty() const { return m_frames.empty(); }
471
472 } // namespace v8_inspector
473