• 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 "src/inspector/v8-debugger.h"
10 #include "src/inspector/v8-inspector-impl.h"
11 #include "src/inspector/wasm-translation.h"
12 
13 namespace v8_inspector {
14 
15 int V8StackTraceImpl::maxCallStackSizeToCapture = 200;
16 
17 namespace {
18 
19 static const v8::StackTrace::StackTraceOptions stackTraceOptions =
20     static_cast<v8::StackTrace::StackTraceOptions>(
21         v8::StackTrace::kDetailed |
22         v8::StackTrace::kExposeFramesAcrossSecurityOrigins);
23 
toFramesVector(V8Debugger * debugger,v8::Local<v8::StackTrace> v8StackTrace,int maxStackSize)24 std::vector<std::shared_ptr<StackFrame>> toFramesVector(
25     V8Debugger* debugger, v8::Local<v8::StackTrace> v8StackTrace,
26     int maxStackSize) {
27   DCHECK(debugger->isolate()->InContext());
28   int frameCount = std::min(v8StackTrace->GetFrameCount(), maxStackSize);
29   std::vector<std::shared_ptr<StackFrame>> frames(frameCount);
30   for (int i = 0; i < frameCount; ++i) {
31     frames[i] =
32         debugger->symbolize(v8StackTrace->GetFrame(debugger->isolate(), i));
33   }
34   return frames;
35 }
36 
calculateAsyncChain(V8Debugger * debugger,int contextGroupId,std::shared_ptr<AsyncStackTrace> * asyncParent,V8StackTraceId * externalParent,int * maxAsyncDepth)37 void calculateAsyncChain(V8Debugger* debugger, int contextGroupId,
38                          std::shared_ptr<AsyncStackTrace>* asyncParent,
39                          V8StackTraceId* externalParent, int* maxAsyncDepth) {
40   *asyncParent = debugger->currentAsyncParent();
41   *externalParent = debugger->currentExternalParent();
42   DCHECK(externalParent->IsInvalid() || !*asyncParent);
43   if (maxAsyncDepth) *maxAsyncDepth = debugger->maxAsyncCallChainDepth();
44 
45   // Do not accidentally append async call chain from another group. This should
46   // not happen if we have proper instrumentation, but let's double-check to be
47   // safe.
48   if (contextGroupId && *asyncParent &&
49       (*asyncParent)->externalParent().IsInvalid() &&
50       (*asyncParent)->contextGroupId() != contextGroupId) {
51     asyncParent->reset();
52     *externalParent = V8StackTraceId();
53     if (maxAsyncDepth) *maxAsyncDepth = 0;
54     return;
55   }
56 
57   // Only the top stack in the chain may be empty, so ensure that second stack
58   // is non-empty (it's the top of appended chain).
59   if (*asyncParent && (*asyncParent)->isEmpty()) {
60     *asyncParent = (*asyncParent)->parent().lock();
61   }
62 }
63 
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)64 std::unique_ptr<protocol::Runtime::StackTrace> buildInspectorObjectCommon(
65     V8Debugger* debugger,
66     const std::vector<std::shared_ptr<StackFrame>>& frames,
67     const String16& description,
68     const std::shared_ptr<AsyncStackTrace>& asyncParent,
69     const V8StackTraceId& externalParent, int maxAsyncDepth) {
70   if (asyncParent && frames.empty() &&
71       description == asyncParent->description()) {
72     return asyncParent->buildInspectorObject(debugger, maxAsyncDepth);
73   }
74 
75   std::unique_ptr<protocol::Array<protocol::Runtime::CallFrame>>
76       inspectorFrames = protocol::Array<protocol::Runtime::CallFrame>::create();
77   for (size_t i = 0; i < frames.size(); i++) {
78     V8InspectorClient* client = nullptr;
79     if (debugger && debugger->inspector())
80       client = debugger->inspector()->client();
81     inspectorFrames->addItem(frames[i]->buildInspectorObject(client));
82   }
83   std::unique_ptr<protocol::Runtime::StackTrace> stackTrace =
84       protocol::Runtime::StackTrace::create()
85           .setCallFrames(std::move(inspectorFrames))
86           .build();
87   if (!description.isEmpty()) stackTrace->setDescription(description);
88   if (asyncParent) {
89     if (maxAsyncDepth > 0) {
90       stackTrace->setParent(
91           asyncParent->buildInspectorObject(debugger, maxAsyncDepth - 1));
92     } else if (debugger) {
93       stackTrace->setParentId(
94           protocol::Runtime::StackTraceId::create()
95               .setId(stackTraceIdToString(
96                   AsyncStackTrace::store(debugger, asyncParent)))
97               .build());
98     }
99   }
100   if (!externalParent.IsInvalid()) {
101     stackTrace->setParentId(
102         protocol::Runtime::StackTraceId::create()
103             .setId(stackTraceIdToString(externalParent.id))
104             .setDebuggerId(debuggerIdToString(externalParent.debugger_id))
105             .build());
106   }
107   return stackTrace;
108 }
109 
110 }  //  namespace
111 
V8StackTraceId()112 V8StackTraceId::V8StackTraceId() : id(0), debugger_id(std::make_pair(0, 0)) {}
113 
V8StackTraceId(uintptr_t id,const std::pair<int64_t,int64_t> debugger_id)114 V8StackTraceId::V8StackTraceId(uintptr_t id,
115                                const std::pair<int64_t, int64_t> debugger_id)
116     : id(id), debugger_id(debugger_id) {}
117 
IsInvalid() const118 bool V8StackTraceId::IsInvalid() const { return !id; }
119 
StackFrame(v8::Isolate * isolate,v8::Local<v8::StackFrame> v8Frame)120 StackFrame::StackFrame(v8::Isolate* isolate, v8::Local<v8::StackFrame> v8Frame)
121     : m_functionName(toProtocolString(isolate, v8Frame->GetFunctionName())),
122       m_scriptId(String16::fromInteger(v8Frame->GetScriptId())),
123       m_sourceURL(
124           toProtocolString(isolate, v8Frame->GetScriptNameOrSourceURL())),
125       m_lineNumber(v8Frame->GetLineNumber() - 1),
126       m_columnNumber(v8Frame->GetColumn() - 1),
127       m_hasSourceURLComment(v8Frame->GetScriptName() !=
128                             v8Frame->GetScriptNameOrSourceURL()) {
129   DCHECK_NE(v8::Message::kNoLineNumberInfo, m_lineNumber + 1);
130   DCHECK_NE(v8::Message::kNoColumnInfo, m_columnNumber + 1);
131 }
132 
translate(WasmTranslation * wasmTranslation)133 void StackFrame::translate(WasmTranslation* wasmTranslation) {
134   wasmTranslation->TranslateWasmScriptLocationToProtocolLocation(
135       &m_scriptId, &m_lineNumber, &m_columnNumber);
136 }
137 
functionName() const138 const String16& StackFrame::functionName() const { return m_functionName; }
139 
scriptId() const140 const String16& StackFrame::scriptId() const { return m_scriptId; }
141 
sourceURL() const142 const String16& StackFrame::sourceURL() const { return m_sourceURL; }
143 
lineNumber() const144 int StackFrame::lineNumber() const { return m_lineNumber; }
145 
columnNumber() const146 int StackFrame::columnNumber() const { return m_columnNumber; }
147 
buildInspectorObject(V8InspectorClient * client) const148 std::unique_ptr<protocol::Runtime::CallFrame> StackFrame::buildInspectorObject(
149     V8InspectorClient* client) const {
150   String16 frameUrl = m_sourceURL;
151   if (client && !m_hasSourceURLComment && frameUrl.length() > 0) {
152     std::unique_ptr<StringBuffer> url =
153         client->resourceNameToUrl(toStringView(m_sourceURL));
154     if (url) {
155       frameUrl = toString16(url->string());
156     }
157   }
158   return protocol::Runtime::CallFrame::create()
159       .setFunctionName(m_functionName)
160       .setScriptId(m_scriptId)
161       .setUrl(frameUrl)
162       .setLineNumber(m_lineNumber)
163       .setColumnNumber(m_columnNumber)
164       .build();
165 }
166 
isEqual(StackFrame * frame) const167 bool StackFrame::isEqual(StackFrame* frame) const {
168   return m_scriptId == frame->m_scriptId &&
169          m_lineNumber == frame->m_lineNumber &&
170          m_columnNumber == frame->m_columnNumber;
171 }
172 
173 // static
setCaptureStackTraceForUncaughtExceptions(v8::Isolate * isolate,bool capture)174 void V8StackTraceImpl::setCaptureStackTraceForUncaughtExceptions(
175     v8::Isolate* isolate, bool capture) {
176   isolate->SetCaptureStackTraceForUncaughtExceptions(
177       capture, V8StackTraceImpl::maxCallStackSizeToCapture);
178 }
179 
180 // static
create(V8Debugger * debugger,int contextGroupId,v8::Local<v8::StackTrace> v8StackTrace,int maxStackSize)181 std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::create(
182     V8Debugger* debugger, int contextGroupId,
183     v8::Local<v8::StackTrace> v8StackTrace, int maxStackSize) {
184   DCHECK(debugger);
185 
186   v8::Isolate* isolate = debugger->isolate();
187   v8::HandleScope scope(isolate);
188 
189   std::vector<std::shared_ptr<StackFrame>> frames;
190   if (!v8StackTrace.IsEmpty() && v8StackTrace->GetFrameCount()) {
191     frames = toFramesVector(debugger, v8StackTrace, maxStackSize);
192   }
193 
194   int maxAsyncDepth = 0;
195   std::shared_ptr<AsyncStackTrace> asyncParent;
196   V8StackTraceId externalParent;
197   calculateAsyncChain(debugger, contextGroupId, &asyncParent, &externalParent,
198                       &maxAsyncDepth);
199   if (frames.empty() && !asyncParent && externalParent.IsInvalid())
200     return nullptr;
201   return std::unique_ptr<V8StackTraceImpl>(new V8StackTraceImpl(
202       std::move(frames), maxAsyncDepth, asyncParent, externalParent));
203 }
204 
205 // static
capture(V8Debugger * debugger,int contextGroupId,int maxStackSize)206 std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::capture(
207     V8Debugger* debugger, int contextGroupId, int maxStackSize) {
208   DCHECK(debugger);
209   v8::Isolate* isolate = debugger->isolate();
210   v8::HandleScope handleScope(isolate);
211   v8::Local<v8::StackTrace> v8StackTrace;
212   if (isolate->InContext()) {
213     v8StackTrace = v8::StackTrace::CurrentStackTrace(isolate, maxStackSize,
214                                                      stackTraceOptions);
215   }
216   return V8StackTraceImpl::create(debugger, contextGroupId, v8StackTrace,
217                                   maxStackSize);
218 }
219 
V8StackTraceImpl(std::vector<std::shared_ptr<StackFrame>> frames,int maxAsyncDepth,std::shared_ptr<AsyncStackTrace> asyncParent,const V8StackTraceId & externalParent)220 V8StackTraceImpl::V8StackTraceImpl(
221     std::vector<std::shared_ptr<StackFrame>> frames, int maxAsyncDepth,
222     std::shared_ptr<AsyncStackTrace> asyncParent,
223     const V8StackTraceId& externalParent)
224     : m_frames(std::move(frames)),
225       m_maxAsyncDepth(maxAsyncDepth),
226       m_asyncParent(asyncParent),
227       m_externalParent(externalParent) {}
228 
~V8StackTraceImpl()229 V8StackTraceImpl::~V8StackTraceImpl() {}
230 
clone()231 std::unique_ptr<V8StackTrace> V8StackTraceImpl::clone() {
232   return std::unique_ptr<V8StackTrace>(new V8StackTraceImpl(
233       m_frames, 0, std::shared_ptr<AsyncStackTrace>(), V8StackTraceId()));
234 }
235 
firstNonEmptySourceURL() const236 StringView V8StackTraceImpl::firstNonEmptySourceURL() const {
237   StackFrameIterator current(this);
238   while (!current.done()) {
239     if (current.frame()->sourceURL().length()) {
240       return toStringView(current.frame()->sourceURL());
241     }
242     current.next();
243   }
244   return StringView();
245 }
246 
isEmpty() const247 bool V8StackTraceImpl::isEmpty() const { return m_frames.empty(); }
248 
topSourceURL() const249 StringView V8StackTraceImpl::topSourceURL() const {
250   return toStringView(m_frames[0]->sourceURL());
251 }
252 
topLineNumber() const253 int V8StackTraceImpl::topLineNumber() const {
254   return m_frames[0]->lineNumber() + 1;
255 }
256 
topColumnNumber() const257 int V8StackTraceImpl::topColumnNumber() const {
258   return m_frames[0]->columnNumber() + 1;
259 }
260 
topScriptId() const261 StringView V8StackTraceImpl::topScriptId() const {
262   return toStringView(m_frames[0]->scriptId());
263 }
264 
topFunctionName() const265 StringView V8StackTraceImpl::topFunctionName() const {
266   return toStringView(m_frames[0]->functionName());
267 }
268 
269 std::unique_ptr<protocol::Runtime::StackTrace>
buildInspectorObjectImpl(V8Debugger * debugger) const270 V8StackTraceImpl::buildInspectorObjectImpl(V8Debugger* debugger) const {
271   return buildInspectorObjectCommon(debugger, m_frames, String16(),
272                                     m_asyncParent.lock(), m_externalParent,
273                                     m_maxAsyncDepth);
274 }
275 
276 std::unique_ptr<protocol::Runtime::API::StackTrace>
buildInspectorObject() const277 V8StackTraceImpl::buildInspectorObject() const {
278   return buildInspectorObjectImpl(nullptr);
279 }
280 
toString() const281 std::unique_ptr<StringBuffer> V8StackTraceImpl::toString() const {
282   String16Builder stackTrace;
283   for (size_t i = 0; i < m_frames.size(); ++i) {
284     const StackFrame& frame = *m_frames[i];
285     stackTrace.append("\n    at " + (frame.functionName().length()
286                                          ? frame.functionName()
287                                          : "(anonymous function)"));
288     stackTrace.append(" (");
289     stackTrace.append(frame.sourceURL());
290     stackTrace.append(':');
291     stackTrace.append(String16::fromInteger(frame.lineNumber() + 1));
292     stackTrace.append(':');
293     stackTrace.append(String16::fromInteger(frame.columnNumber() + 1));
294     stackTrace.append(')');
295   }
296   String16 string = stackTrace.toString();
297   return StringBufferImpl::adopt(string);
298 }
299 
isEqualIgnoringTopFrame(V8StackTraceImpl * stackTrace) const300 bool V8StackTraceImpl::isEqualIgnoringTopFrame(
301     V8StackTraceImpl* stackTrace) const {
302   StackFrameIterator current(this);
303   StackFrameIterator target(stackTrace);
304 
305   current.next();
306   target.next();
307   while (!current.done() && !target.done()) {
308     if (!current.frame()->isEqual(target.frame())) {
309       return false;
310     }
311     current.next();
312     target.next();
313   }
314   return current.done() == target.done();
315 }
316 
StackFrameIterator(const V8StackTraceImpl * stackTrace)317 V8StackTraceImpl::StackFrameIterator::StackFrameIterator(
318     const V8StackTraceImpl* stackTrace)
319     : m_currentIt(stackTrace->m_frames.begin()),
320       m_currentEnd(stackTrace->m_frames.end()),
321       m_parent(stackTrace->m_asyncParent.lock().get()) {}
322 
next()323 void V8StackTraceImpl::StackFrameIterator::next() {
324   if (m_currentIt == m_currentEnd) return;
325   ++m_currentIt;
326   while (m_currentIt == m_currentEnd && m_parent) {
327     const std::vector<std::shared_ptr<StackFrame>>& frames = m_parent->frames();
328     m_currentIt = frames.begin();
329     if (m_parent->description() == "async function") ++m_currentIt;
330     m_currentEnd = frames.end();
331     m_parent = m_parent->parent().lock().get();
332   }
333 }
334 
done()335 bool V8StackTraceImpl::StackFrameIterator::done() {
336   return m_currentIt == m_currentEnd;
337 }
338 
frame()339 StackFrame* V8StackTraceImpl::StackFrameIterator::frame() {
340   return m_currentIt->get();
341 }
342 
343 // static
capture(V8Debugger * debugger,int contextGroupId,const String16 & description,int maxStackSize)344 std::shared_ptr<AsyncStackTrace> AsyncStackTrace::capture(
345     V8Debugger* debugger, int contextGroupId, const String16& description,
346     int maxStackSize) {
347   DCHECK(debugger);
348 
349   v8::Isolate* isolate = debugger->isolate();
350   v8::HandleScope handleScope(isolate);
351 
352   std::vector<std::shared_ptr<StackFrame>> frames;
353   if (isolate->InContext()) {
354     v8::Local<v8::StackTrace> v8StackTrace = v8::StackTrace::CurrentStackTrace(
355         isolate, maxStackSize, stackTraceOptions);
356     frames = toFramesVector(debugger, v8StackTrace, maxStackSize);
357   }
358 
359   std::shared_ptr<AsyncStackTrace> asyncParent;
360   V8StackTraceId externalParent;
361   calculateAsyncChain(debugger, contextGroupId, &asyncParent, &externalParent,
362                       nullptr);
363 
364   if (frames.empty() && !asyncParent && externalParent.IsInvalid())
365     return nullptr;
366 
367   // When async call chain is empty but doesn't contain useful schedule stack
368   // but doesn't synchronous we can merge them together. e.g. Promise
369   // ThenableJob.
370   if (asyncParent && frames.empty() &&
371       (asyncParent->m_description == description || description.isEmpty())) {
372     return asyncParent;
373   }
374 
375   DCHECK(contextGroupId || asyncParent || !externalParent.IsInvalid());
376   if (!contextGroupId && asyncParent) {
377     contextGroupId = asyncParent->m_contextGroupId;
378   }
379 
380   return std::shared_ptr<AsyncStackTrace>(
381       new AsyncStackTrace(contextGroupId, description, std::move(frames),
382                           asyncParent, externalParent));
383 }
384 
AsyncStackTrace(int contextGroupId,const String16 & description,std::vector<std::shared_ptr<StackFrame>> frames,std::shared_ptr<AsyncStackTrace> asyncParent,const V8StackTraceId & externalParent)385 AsyncStackTrace::AsyncStackTrace(
386     int contextGroupId, const String16& description,
387     std::vector<std::shared_ptr<StackFrame>> frames,
388     std::shared_ptr<AsyncStackTrace> asyncParent,
389     const V8StackTraceId& externalParent)
390     : m_contextGroupId(contextGroupId),
391       m_id(0),
392       m_suspendedTaskId(nullptr),
393       m_description(description),
394       m_frames(std::move(frames)),
395       m_asyncParent(asyncParent),
396       m_externalParent(externalParent) {
397   DCHECK(m_contextGroupId || (!externalParent.IsInvalid() && m_frames.empty()));
398 }
399 
400 std::unique_ptr<protocol::Runtime::StackTrace>
buildInspectorObject(V8Debugger * debugger,int maxAsyncDepth) const401 AsyncStackTrace::buildInspectorObject(V8Debugger* debugger,
402                                       int maxAsyncDepth) const {
403   return buildInspectorObjectCommon(debugger, m_frames, m_description,
404                                     m_asyncParent.lock(), m_externalParent,
405                                     maxAsyncDepth);
406 }
407 
contextGroupId() const408 int AsyncStackTrace::contextGroupId() const { return m_contextGroupId; }
409 
setSuspendedTaskId(void * task)410 void AsyncStackTrace::setSuspendedTaskId(void* task) {
411   m_suspendedTaskId = task;
412 }
413 
suspendedTaskId() const414 void* AsyncStackTrace::suspendedTaskId() const { return m_suspendedTaskId; }
415 
store(V8Debugger * debugger,std::shared_ptr<AsyncStackTrace> stack)416 uintptr_t AsyncStackTrace::store(V8Debugger* debugger,
417                                  std::shared_ptr<AsyncStackTrace> stack) {
418   if (stack->m_id) return stack->m_id;
419   stack->m_id = debugger->storeStackTrace(stack);
420   return stack->m_id;
421 }
422 
description() const423 const String16& AsyncStackTrace::description() const { return m_description; }
424 
parent() const425 std::weak_ptr<AsyncStackTrace> AsyncStackTrace::parent() const {
426   return m_asyncParent;
427 }
428 
isEmpty() const429 bool AsyncStackTrace::isEmpty() const { return m_frames.empty(); }
430 
431 }  // namespace v8_inspector
432