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/inspector/v8-stack-trace-impl.h"
6
7 #include <algorithm>
8
9 #include "../../third_party/inspector_protocol/crdtp/json.h"
10 #include "src/inspector/v8-debugger.h"
11 #include "src/inspector/v8-inspector-impl.h"
12 #include "src/tracing/trace-event.h"
13
14 using v8_crdtp::SpanFrom;
15 using v8_crdtp::json::ConvertCBORToJSON;
16 using v8_crdtp::json::ConvertJSONToCBOR;
17
18 namespace v8_inspector {
19
20 int V8StackTraceImpl::maxCallStackSizeToCapture = 200;
21
22 namespace {
23
24 static const char kId[] = "id";
25 static const char kDebuggerId[] = "debuggerId";
26 static const char kShouldPause[] = "shouldPause";
27
28 static const v8::StackTrace::StackTraceOptions stackTraceOptions =
29 static_cast<v8::StackTrace::StackTraceOptions>(
30 v8::StackTrace::kDetailed |
31 v8::StackTrace::kExposeFramesAcrossSecurityOrigins);
32
toFramesVector(V8Debugger * debugger,v8::Local<v8::StackTrace> v8StackTrace,int maxStackSize)33 std::vector<std::shared_ptr<StackFrame>> toFramesVector(
34 V8Debugger* debugger, v8::Local<v8::StackTrace> v8StackTrace,
35 int maxStackSize) {
36 DCHECK(debugger->isolate()->InContext());
37 int frameCount = std::min(v8StackTrace->GetFrameCount(), maxStackSize);
38
39 TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("v8.stack_trace"),
40 "SymbolizeStackTrace", "frameCount", frameCount);
41
42 std::vector<std::shared_ptr<StackFrame>> frames(frameCount);
43 for (int i = 0; i < frameCount; ++i) {
44 frames[i] =
45 debugger->symbolize(v8StackTrace->GetFrame(debugger->isolate(), i));
46 }
47 return frames;
48 }
49
calculateAsyncChain(V8Debugger * debugger,int contextGroupId,std::shared_ptr<AsyncStackTrace> * asyncParent,V8StackTraceId * externalParent,int * maxAsyncDepth)50 void calculateAsyncChain(V8Debugger* debugger, int contextGroupId,
51 std::shared_ptr<AsyncStackTrace>* asyncParent,
52 V8StackTraceId* externalParent, int* maxAsyncDepth) {
53 *asyncParent = debugger->currentAsyncParent();
54 *externalParent = debugger->currentExternalParent();
55 DCHECK(externalParent->IsInvalid() || !*asyncParent);
56 if (maxAsyncDepth) *maxAsyncDepth = debugger->maxAsyncCallChainDepth();
57
58 // Do not accidentally append async call chain from another group. This should
59 // not happen if we have proper instrumentation, but let's double-check to be
60 // safe.
61 if (contextGroupId && *asyncParent &&
62 (*asyncParent)->externalParent().IsInvalid() &&
63 (*asyncParent)->contextGroupId() != contextGroupId) {
64 asyncParent->reset();
65 *externalParent = V8StackTraceId();
66 if (maxAsyncDepth) *maxAsyncDepth = 0;
67 return;
68 }
69
70 // Only the top stack in the chain may be empty, so ensure that second stack
71 // is non-empty (it's the top of appended chain).
72 if (*asyncParent && (*asyncParent)->isEmpty()) {
73 *asyncParent = (*asyncParent)->parent().lock();
74 }
75 }
76
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)77 std::unique_ptr<protocol::Runtime::StackTrace> buildInspectorObjectCommon(
78 V8Debugger* debugger,
79 const std::vector<std::shared_ptr<StackFrame>>& frames,
80 const String16& description,
81 const std::shared_ptr<AsyncStackTrace>& asyncParent,
82 const V8StackTraceId& externalParent, int maxAsyncDepth) {
83 if (asyncParent && frames.empty() &&
84 description == asyncParent->description()) {
85 return asyncParent->buildInspectorObject(debugger, maxAsyncDepth);
86 }
87
88 auto inspectorFrames =
89 std::make_unique<protocol::Array<protocol::Runtime::CallFrame>>();
90 for (const std::shared_ptr<StackFrame>& frame : frames) {
91 V8InspectorClient* client = nullptr;
92 if (debugger && debugger->inspector())
93 client = debugger->inspector()->client();
94 inspectorFrames->emplace_back(frame->buildInspectorObject(client));
95 }
96 std::unique_ptr<protocol::Runtime::StackTrace> stackTrace =
97 protocol::Runtime::StackTrace::create()
98 .setCallFrames(std::move(inspectorFrames))
99 .build();
100 if (!description.isEmpty()) stackTrace->setDescription(description);
101 if (asyncParent) {
102 if (maxAsyncDepth > 0) {
103 stackTrace->setParent(
104 asyncParent->buildInspectorObject(debugger, maxAsyncDepth - 1));
105 } else if (debugger) {
106 stackTrace->setParentId(
107 protocol::Runtime::StackTraceId::create()
108 .setId(stackTraceIdToString(
109 AsyncStackTrace::store(debugger, asyncParent)))
110 .build());
111 }
112 }
113 if (!externalParent.IsInvalid()) {
114 stackTrace->setParentId(
115 protocol::Runtime::StackTraceId::create()
116 .setId(stackTraceIdToString(externalParent.id))
117 .setDebuggerId(V8DebuggerId(externalParent.debugger_id).toString())
118 .build());
119 }
120 return stackTrace;
121 }
122
123 } // namespace
124
V8StackTraceId()125 V8StackTraceId::V8StackTraceId() : id(0), debugger_id(V8DebuggerId().pair()) {}
126
V8StackTraceId(uintptr_t id,const std::pair<int64_t,int64_t> debugger_id)127 V8StackTraceId::V8StackTraceId(uintptr_t id,
128 const std::pair<int64_t, int64_t> debugger_id)
129 : id(id), debugger_id(debugger_id) {}
130
V8StackTraceId(uintptr_t id,const std::pair<int64_t,int64_t> debugger_id,bool should_pause)131 V8StackTraceId::V8StackTraceId(uintptr_t id,
132 const std::pair<int64_t, int64_t> debugger_id,
133 bool should_pause)
134 : id(id), debugger_id(debugger_id), should_pause(should_pause) {}
135
V8StackTraceId(StringView json)136 V8StackTraceId::V8StackTraceId(StringView json)
137 : id(0), debugger_id(V8DebuggerId().pair()) {
138 if (json.length() == 0) return;
139 std::vector<uint8_t> cbor;
140 if (json.is8Bit()) {
141 ConvertJSONToCBOR(
142 v8_crdtp::span<uint8_t>(json.characters8(), json.length()), &cbor);
143 } else {
144 ConvertJSONToCBOR(
145 v8_crdtp::span<uint16_t>(json.characters16(), json.length()), &cbor);
146 }
147 auto dict = protocol::DictionaryValue::cast(
148 protocol::Value::parseBinary(cbor.data(), cbor.size()));
149 if (!dict) return;
150 String16 s;
151 if (!dict->getString(kId, &s)) return;
152 bool isOk = false;
153 int64_t parsedId = s.toInteger64(&isOk);
154 if (!isOk || !parsedId) return;
155 if (!dict->getString(kDebuggerId, &s)) return;
156 V8DebuggerId debuggerId(s);
157 if (!debuggerId.isValid()) return;
158 if (!dict->getBoolean(kShouldPause, &should_pause)) return;
159 id = parsedId;
160 debugger_id = debuggerId.pair();
161 }
162
IsInvalid() const163 bool V8StackTraceId::IsInvalid() const { return !id; }
164
ToString()165 std::unique_ptr<StringBuffer> V8StackTraceId::ToString() {
166 if (IsInvalid()) return nullptr;
167 auto dict = protocol::DictionaryValue::create();
168 dict->setString(kId, String16::fromInteger64(id));
169 dict->setString(kDebuggerId, V8DebuggerId(debugger_id).toString());
170 dict->setBoolean(kShouldPause, should_pause);
171 std::vector<uint8_t> json;
172 v8_crdtp::json::ConvertCBORToJSON(v8_crdtp::SpanFrom(dict->Serialize()),
173 &json);
174 return StringBufferFrom(std::move(json));
175 }
176
StackFrame(v8::Isolate * isolate,v8::Local<v8::StackFrame> v8Frame)177 StackFrame::StackFrame(v8::Isolate* isolate, v8::Local<v8::StackFrame> v8Frame)
178 : m_functionName(toProtocolString(isolate, v8Frame->GetFunctionName())),
179 m_scriptId(String16::fromInteger(v8Frame->GetScriptId())),
180 m_sourceURL(
181 toProtocolString(isolate, v8Frame->GetScriptNameOrSourceURL())),
182 m_lineNumber(v8Frame->GetLineNumber() - 1),
183 m_columnNumber(v8Frame->GetColumn() - 1),
184 m_hasSourceURLComment(v8Frame->GetScriptName() !=
185 v8Frame->GetScriptNameOrSourceURL()) {
186 DCHECK_NE(v8::Message::kNoLineNumberInfo, m_lineNumber + 1);
187 DCHECK_NE(v8::Message::kNoColumnInfo, m_columnNumber + 1);
188 }
189
functionName() const190 const String16& StackFrame::functionName() const { return m_functionName; }
191
scriptId() const192 const String16& StackFrame::scriptId() const { return m_scriptId; }
193
sourceURL() const194 const String16& StackFrame::sourceURL() const { return m_sourceURL; }
195
lineNumber() const196 int StackFrame::lineNumber() const { return m_lineNumber; }
197
columnNumber() const198 int StackFrame::columnNumber() const { return m_columnNumber; }
199
buildInspectorObject(V8InspectorClient * client) const200 std::unique_ptr<protocol::Runtime::CallFrame> StackFrame::buildInspectorObject(
201 V8InspectorClient* client) const {
202 String16 frameUrl = m_sourceURL;
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(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
setCaptureStackTraceForUncaughtExceptions(v8::Isolate * isolate,bool capture)226 void V8StackTraceImpl::setCaptureStackTraceForUncaughtExceptions(
227 v8::Isolate* isolate, bool capture) {
228 isolate->SetCaptureStackTraceForUncaughtExceptions(
229 capture, V8StackTraceImpl::maxCallStackSizeToCapture);
230 }
231
232 // static
create(V8Debugger * debugger,int contextGroupId,v8::Local<v8::StackTrace> v8StackTrace,int maxStackSize)233 std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::create(
234 V8Debugger* debugger, int contextGroupId,
235 v8::Local<v8::StackTrace> v8StackTrace, int maxStackSize) {
236 DCHECK(debugger);
237
238 v8::Isolate* isolate = debugger->isolate();
239 v8::HandleScope scope(isolate);
240
241 std::vector<std::shared_ptr<StackFrame>> frames;
242 if (!v8StackTrace.IsEmpty() && v8StackTrace->GetFrameCount()) {
243 frames = toFramesVector(debugger, v8StackTrace, maxStackSize);
244 }
245
246 int maxAsyncDepth = 0;
247 std::shared_ptr<AsyncStackTrace> asyncParent;
248 V8StackTraceId externalParent;
249 calculateAsyncChain(debugger, contextGroupId, &asyncParent, &externalParent,
250 &maxAsyncDepth);
251 if (frames.empty() && !asyncParent && externalParent.IsInvalid())
252 return nullptr;
253 return std::unique_ptr<V8StackTraceImpl>(new V8StackTraceImpl(
254 std::move(frames), maxAsyncDepth, asyncParent, externalParent));
255 }
256
257 // static
capture(V8Debugger * debugger,int contextGroupId,int maxStackSize)258 std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::capture(
259 V8Debugger* debugger, int contextGroupId, int maxStackSize) {
260 DCHECK(debugger);
261
262 TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("v8.stack_trace"),
263 "V8StackTraceImpl::capture", "maxFrameCount", maxStackSize);
264
265 v8::Isolate* isolate = debugger->isolate();
266 v8::HandleScope handleScope(isolate);
267 v8::Local<v8::StackTrace> v8StackTrace;
268 if (isolate->InContext()) {
269 v8StackTrace = v8::StackTrace::CurrentStackTrace(isolate, maxStackSize,
270 stackTraceOptions);
271 }
272 return V8StackTraceImpl::create(debugger, contextGroupId, v8StackTrace,
273 maxStackSize);
274 }
275
V8StackTraceImpl(std::vector<std::shared_ptr<StackFrame>> frames,int maxAsyncDepth,std::shared_ptr<AsyncStackTrace> asyncParent,const V8StackTraceId & externalParent)276 V8StackTraceImpl::V8StackTraceImpl(
277 std::vector<std::shared_ptr<StackFrame>> frames, int maxAsyncDepth,
278 std::shared_ptr<AsyncStackTrace> asyncParent,
279 const V8StackTraceId& externalParent)
280 : m_frames(std::move(frames)),
281 m_maxAsyncDepth(maxAsyncDepth),
282 m_asyncParent(std::move(asyncParent)),
283 m_externalParent(externalParent) {}
284
285 V8StackTraceImpl::~V8StackTraceImpl() = default;
286
clone()287 std::unique_ptr<V8StackTrace> V8StackTraceImpl::clone() {
288 return std::unique_ptr<V8StackTrace>(new V8StackTraceImpl(
289 m_frames, 0, std::shared_ptr<AsyncStackTrace>(), V8StackTraceId()));
290 }
291
firstNonEmptySourceURL() const292 StringView V8StackTraceImpl::firstNonEmptySourceURL() const {
293 StackFrameIterator current(this);
294 while (!current.done()) {
295 if (current.frame()->sourceURL().length()) {
296 return toStringView(current.frame()->sourceURL());
297 }
298 current.next();
299 }
300 return StringView();
301 }
302
isEmpty() const303 bool V8StackTraceImpl::isEmpty() const { return m_frames.empty(); }
304
topSourceURL() const305 StringView V8StackTraceImpl::topSourceURL() const {
306 return toStringView(m_frames[0]->sourceURL());
307 }
308
topLineNumber() const309 int V8StackTraceImpl::topLineNumber() const {
310 return m_frames[0]->lineNumber() + 1;
311 }
312
topColumnNumber() const313 int V8StackTraceImpl::topColumnNumber() const {
314 return m_frames[0]->columnNumber() + 1;
315 }
316
topScriptId() const317 StringView V8StackTraceImpl::topScriptId() const {
318 return toStringView(m_frames[0]->scriptId());
319 }
320
topFunctionName() const321 StringView V8StackTraceImpl::topFunctionName() const {
322 return toStringView(m_frames[0]->functionName());
323 }
324
325 std::unique_ptr<protocol::Runtime::StackTrace>
buildInspectorObjectImpl(V8Debugger * debugger) const326 V8StackTraceImpl::buildInspectorObjectImpl(V8Debugger* debugger) const {
327 return buildInspectorObjectImpl(debugger, m_maxAsyncDepth);
328 }
329
330 std::unique_ptr<protocol::Runtime::StackTrace>
buildInspectorObjectImpl(V8Debugger * debugger,int maxAsyncDepth) const331 V8StackTraceImpl::buildInspectorObjectImpl(V8Debugger* debugger,
332 int maxAsyncDepth) const {
333 return buildInspectorObjectCommon(debugger, m_frames, String16(),
334 m_asyncParent.lock(), m_externalParent,
335 maxAsyncDepth);
336 }
337
338 std::unique_ptr<protocol::Runtime::API::StackTrace>
buildInspectorObject() const339 V8StackTraceImpl::buildInspectorObject() const {
340 return buildInspectorObjectImpl(nullptr);
341 }
342
343 std::unique_ptr<protocol::Runtime::API::StackTrace>
buildInspectorObject(int maxAsyncDepth) const344 V8StackTraceImpl::buildInspectorObject(int maxAsyncDepth) const {
345 return buildInspectorObjectImpl(nullptr,
346 std::min(maxAsyncDepth, m_maxAsyncDepth));
347 }
348
toString() const349 std::unique_ptr<StringBuffer> V8StackTraceImpl::toString() const {
350 String16Builder stackTrace;
351 for (size_t i = 0; i < m_frames.size(); ++i) {
352 const StackFrame& frame = *m_frames[i];
353 stackTrace.append("\n at " + (frame.functionName().length()
354 ? frame.functionName()
355 : "(anonymous function)"));
356 stackTrace.append(" (");
357 stackTrace.append(frame.sourceURL());
358 stackTrace.append(':');
359 stackTrace.append(String16::fromInteger(frame.lineNumber() + 1));
360 stackTrace.append(':');
361 stackTrace.append(String16::fromInteger(frame.columnNumber() + 1));
362 stackTrace.append(')');
363 }
364 return StringBufferFrom(stackTrace.toString());
365 }
366
isEqualIgnoringTopFrame(V8StackTraceImpl * stackTrace) const367 bool V8StackTraceImpl::isEqualIgnoringTopFrame(
368 V8StackTraceImpl* stackTrace) const {
369 StackFrameIterator current(this);
370 StackFrameIterator target(stackTrace);
371
372 current.next();
373 target.next();
374 while (!current.done() && !target.done()) {
375 if (!current.frame()->isEqual(target.frame())) {
376 return false;
377 }
378 current.next();
379 target.next();
380 }
381 return current.done() == target.done();
382 }
383
StackFrameIterator(const V8StackTraceImpl * stackTrace)384 V8StackTraceImpl::StackFrameIterator::StackFrameIterator(
385 const V8StackTraceImpl* stackTrace)
386 : m_currentIt(stackTrace->m_frames.begin()),
387 m_currentEnd(stackTrace->m_frames.end()),
388 m_parent(stackTrace->m_asyncParent.lock().get()) {}
389
next()390 void V8StackTraceImpl::StackFrameIterator::next() {
391 if (m_currentIt == m_currentEnd) return;
392 ++m_currentIt;
393 while (m_currentIt == m_currentEnd && m_parent) {
394 const std::vector<std::shared_ptr<StackFrame>>& frames = m_parent->frames();
395 m_currentIt = frames.begin();
396 if (m_parent->description() == "async function") ++m_currentIt;
397 m_currentEnd = frames.end();
398 m_parent = m_parent->parent().lock().get();
399 }
400 }
401
done()402 bool V8StackTraceImpl::StackFrameIterator::done() {
403 return m_currentIt == m_currentEnd;
404 }
405
frame()406 StackFrame* V8StackTraceImpl::StackFrameIterator::frame() {
407 return m_currentIt->get();
408 }
409
410 // static
capture(V8Debugger * debugger,int contextGroupId,const String16 & description,int maxStackSize)411 std::shared_ptr<AsyncStackTrace> AsyncStackTrace::capture(
412 V8Debugger* debugger, int contextGroupId, const String16& description,
413 int maxStackSize) {
414 DCHECK(debugger);
415
416 TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("v8.stack_trace"),
417 "AsyncStackTrace::capture", "maxFrameCount", maxStackSize);
418
419 v8::Isolate* isolate = debugger->isolate();
420 v8::HandleScope handleScope(isolate);
421
422 std::vector<std::shared_ptr<StackFrame>> frames;
423 if (isolate->InContext()) {
424 v8::Local<v8::StackTrace> v8StackTrace = v8::StackTrace::CurrentStackTrace(
425 isolate, maxStackSize, stackTraceOptions);
426 frames = toFramesVector(debugger, v8StackTrace, maxStackSize);
427 }
428
429 std::shared_ptr<AsyncStackTrace> asyncParent;
430 V8StackTraceId externalParent;
431 calculateAsyncChain(debugger, contextGroupId, &asyncParent, &externalParent,
432 nullptr);
433
434 if (frames.empty() && !asyncParent && externalParent.IsInvalid())
435 return nullptr;
436
437 // When async call chain is empty but doesn't contain useful schedule stack
438 // but doesn't synchronous we can merge them together. e.g. Promise
439 // ThenableJob.
440 if (asyncParent && frames.empty() &&
441 (asyncParent->m_description == description || description.isEmpty())) {
442 return asyncParent;
443 }
444
445 DCHECK(contextGroupId || asyncParent || !externalParent.IsInvalid());
446 if (!contextGroupId && asyncParent) {
447 contextGroupId = asyncParent->m_contextGroupId;
448 }
449
450 return std::shared_ptr<AsyncStackTrace>(
451 new AsyncStackTrace(contextGroupId, description, std::move(frames),
452 asyncParent, externalParent));
453 }
454
AsyncStackTrace(int contextGroupId,const String16 & description,std::vector<std::shared_ptr<StackFrame>> frames,std::shared_ptr<AsyncStackTrace> asyncParent,const V8StackTraceId & externalParent)455 AsyncStackTrace::AsyncStackTrace(
456 int contextGroupId, const String16& description,
457 std::vector<std::shared_ptr<StackFrame>> frames,
458 std::shared_ptr<AsyncStackTrace> asyncParent,
459 const V8StackTraceId& externalParent)
460 : m_contextGroupId(contextGroupId),
461 m_id(0),
462 m_suspendedTaskId(nullptr),
463 m_description(description),
464 m_frames(std::move(frames)),
465 m_asyncParent(std::move(asyncParent)),
466 m_externalParent(externalParent) {
467 DCHECK(m_contextGroupId || (!externalParent.IsInvalid() && m_frames.empty()));
468 }
469
470 std::unique_ptr<protocol::Runtime::StackTrace>
buildInspectorObject(V8Debugger * debugger,int maxAsyncDepth) const471 AsyncStackTrace::buildInspectorObject(V8Debugger* debugger,
472 int maxAsyncDepth) const {
473 return buildInspectorObjectCommon(debugger, m_frames, m_description,
474 m_asyncParent.lock(), m_externalParent,
475 maxAsyncDepth);
476 }
477
contextGroupId() const478 int AsyncStackTrace::contextGroupId() const { return m_contextGroupId; }
479
setSuspendedTaskId(void * task)480 void AsyncStackTrace::setSuspendedTaskId(void* task) {
481 m_suspendedTaskId = task;
482 }
483
suspendedTaskId() const484 void* AsyncStackTrace::suspendedTaskId() const { return m_suspendedTaskId; }
485
store(V8Debugger * debugger,std::shared_ptr<AsyncStackTrace> stack)486 uintptr_t AsyncStackTrace::store(V8Debugger* debugger,
487 std::shared_ptr<AsyncStackTrace> stack) {
488 if (stack->m_id) return stack->m_id;
489 stack->m_id = debugger->storeStackTrace(stack);
490 return stack->m_id;
491 }
492
description() const493 const String16& AsyncStackTrace::description() const { return m_description; }
494
parent() const495 std::weak_ptr<AsyncStackTrace> AsyncStackTrace::parent() const {
496 return m_asyncParent;
497 }
498
isEmpty() const499 bool AsyncStackTrace::isEmpty() const { return m_frames.empty(); }
500
501 } // namespace v8_inspector
502