• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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