V8 API Reference, 7.2.502.16 (for Deno 0.2.4)
v8-stack-trace-impl.cc
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 =
21  v8::StackTrace::kDetailed |
22  v8::StackTrace::kExposeFramesAcrossSecurityOrigins);
23 
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 
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 
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 
112 V8StackTraceId::V8StackTraceId() : id(0), debugger_id(std::make_pair(0, 0)) {}
113 
114 V8StackTraceId::V8StackTraceId(uintptr_t id,
115  const std::pair<int64_t, int64_t> debugger_id)
116  : id(id), debugger_id(debugger_id) {}
117 
118 bool V8StackTraceId::IsInvalid() const { return !id; }
119 
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 
133 void StackFrame::translate(WasmTranslation* wasmTranslation) {
134  wasmTranslation->TranslateWasmScriptLocationToProtocolLocation(
135  &m_scriptId, &m_lineNumber, &m_columnNumber);
136 }
137 
138 const String16& StackFrame::functionName() const { return m_functionName; }
139 
140 const String16& StackFrame::scriptId() const { return m_scriptId; }
141 
142 const String16& StackFrame::sourceURL() const { return m_sourceURL; }
143 
144 int StackFrame::lineNumber() const { return m_lineNumber; }
145 
146 int StackFrame::columnNumber() const { return m_columnNumber; }
147 
148 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 
167 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
174 void V8StackTraceImpl::setCaptureStackTraceForUncaughtExceptions(
175  v8::Isolate* isolate, bool capture) {
176  isolate->SetCaptureStackTraceForUncaughtExceptions(
177  capture, V8StackTraceImpl::maxCallStackSizeToCapture);
178 }
179 
180 // static
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
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 
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(std::move(asyncParent)),
227  m_externalParent(externalParent) {}
228 
229 V8StackTraceImpl::~V8StackTraceImpl() = default;
230 
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 
236 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 
247 bool V8StackTraceImpl::isEmpty() const { return m_frames.empty(); }
248 
249 StringView V8StackTraceImpl::topSourceURL() const {
250  return toStringView(m_frames[0]->sourceURL());
251 }
252 
253 int V8StackTraceImpl::topLineNumber() const {
254  return m_frames[0]->lineNumber() + 1;
255 }
256 
257 int V8StackTraceImpl::topColumnNumber() const {
258  return m_frames[0]->columnNumber() + 1;
259 }
260 
261 StringView V8StackTraceImpl::topScriptId() const {
262  return toStringView(m_frames[0]->scriptId());
263 }
264 
265 StringView V8StackTraceImpl::topFunctionName() const {
266  return toStringView(m_frames[0]->functionName());
267 }
268 
269 std::unique_ptr<protocol::Runtime::StackTrace>
270 V8StackTraceImpl::buildInspectorObjectImpl(V8Debugger* debugger) const {
271  return buildInspectorObjectImpl(debugger, m_maxAsyncDepth);
272 }
273 
274 std::unique_ptr<protocol::Runtime::StackTrace>
275 V8StackTraceImpl::buildInspectorObjectImpl(V8Debugger* debugger,
276  int maxAsyncDepth) const {
277  return buildInspectorObjectCommon(debugger, m_frames, String16(),
278  m_asyncParent.lock(), m_externalParent,
279  maxAsyncDepth);
280 }
281 
282 std::unique_ptr<protocol::Runtime::API::StackTrace>
283 V8StackTraceImpl::buildInspectorObject() const {
284  return buildInspectorObjectImpl(nullptr);
285 }
286 
287 std::unique_ptr<StringBuffer> V8StackTraceImpl::toString() const {
288  String16Builder stackTrace;
289  for (size_t i = 0; i < m_frames.size(); ++i) {
290  const StackFrame& frame = *m_frames[i];
291  stackTrace.append("\n at " + (frame.functionName().length()
292  ? frame.functionName()
293  : "(anonymous function)"));
294  stackTrace.append(" (");
295  stackTrace.append(frame.sourceURL());
296  stackTrace.append(':');
297  stackTrace.append(String16::fromInteger(frame.lineNumber() + 1));
298  stackTrace.append(':');
299  stackTrace.append(String16::fromInteger(frame.columnNumber() + 1));
300  stackTrace.append(')');
301  }
302  String16 string = stackTrace.toString();
303  return StringBufferImpl::adopt(string);
304 }
305 
306 bool V8StackTraceImpl::isEqualIgnoringTopFrame(
307  V8StackTraceImpl* stackTrace) const {
308  StackFrameIterator current(this);
309  StackFrameIterator target(stackTrace);
310 
311  current.next();
312  target.next();
313  while (!current.done() && !target.done()) {
314  if (!current.frame()->isEqual(target.frame())) {
315  return false;
316  }
317  current.next();
318  target.next();
319  }
320  return current.done() == target.done();
321 }
322 
323 V8StackTraceImpl::StackFrameIterator::StackFrameIterator(
324  const V8StackTraceImpl* stackTrace)
325  : m_currentIt(stackTrace->m_frames.begin()),
326  m_currentEnd(stackTrace->m_frames.end()),
327  m_parent(stackTrace->m_asyncParent.lock().get()) {}
328 
329 void V8StackTraceImpl::StackFrameIterator::next() {
330  if (m_currentIt == m_currentEnd) return;
331  ++m_currentIt;
332  while (m_currentIt == m_currentEnd && m_parent) {
333  const std::vector<std::shared_ptr<StackFrame>>& frames = m_parent->frames();
334  m_currentIt = frames.begin();
335  if (m_parent->description() == "async function") ++m_currentIt;
336  m_currentEnd = frames.end();
337  m_parent = m_parent->parent().lock().get();
338  }
339 }
340 
341 bool V8StackTraceImpl::StackFrameIterator::done() {
342  return m_currentIt == m_currentEnd;
343 }
344 
345 StackFrame* V8StackTraceImpl::StackFrameIterator::frame() {
346  return m_currentIt->get();
347 }
348 
349 // static
350 std::shared_ptr<AsyncStackTrace> AsyncStackTrace::capture(
351  V8Debugger* debugger, int contextGroupId, const String16& description,
352  int maxStackSize) {
353  DCHECK(debugger);
354 
355  v8::Isolate* isolate = debugger->isolate();
356  v8::HandleScope handleScope(isolate);
357 
358  std::vector<std::shared_ptr<StackFrame>> frames;
359  if (isolate->InContext()) {
361  isolate, maxStackSize, stackTraceOptions);
362  frames = toFramesVector(debugger, v8StackTrace, maxStackSize);
363  }
364 
365  std::shared_ptr<AsyncStackTrace> asyncParent;
366  V8StackTraceId externalParent;
367  calculateAsyncChain(debugger, contextGroupId, &asyncParent, &externalParent,
368  nullptr);
369 
370  if (frames.empty() && !asyncParent && externalParent.IsInvalid())
371  return nullptr;
372 
373  // When async call chain is empty but doesn't contain useful schedule stack
374  // but doesn't synchronous we can merge them together. e.g. Promise
375  // ThenableJob.
376  if (asyncParent && frames.empty() &&
377  (asyncParent->m_description == description || description.isEmpty())) {
378  return asyncParent;
379  }
380 
381  DCHECK(contextGroupId || asyncParent || !externalParent.IsInvalid());
382  if (!contextGroupId && asyncParent) {
383  contextGroupId = asyncParent->m_contextGroupId;
384  }
385 
386  return std::shared_ptr<AsyncStackTrace>(
387  new AsyncStackTrace(contextGroupId, description, std::move(frames),
388  asyncParent, externalParent));
389 }
390 
391 AsyncStackTrace::AsyncStackTrace(
392  int contextGroupId, const String16& description,
393  std::vector<std::shared_ptr<StackFrame>> frames,
394  std::shared_ptr<AsyncStackTrace> asyncParent,
395  const V8StackTraceId& externalParent)
396  : m_contextGroupId(contextGroupId),
397  m_id(0),
398  m_suspendedTaskId(nullptr),
399  m_description(description),
400  m_frames(std::move(frames)),
401  m_asyncParent(std::move(asyncParent)),
402  m_externalParent(externalParent) {
403  DCHECK(m_contextGroupId || (!externalParent.IsInvalid() && m_frames.empty()));
404 }
405 
406 std::unique_ptr<protocol::Runtime::StackTrace>
407 AsyncStackTrace::buildInspectorObject(V8Debugger* debugger,
408  int maxAsyncDepth) const {
409  return buildInspectorObjectCommon(debugger, m_frames, m_description,
410  m_asyncParent.lock(), m_externalParent,
411  maxAsyncDepth);
412 }
413 
414 int AsyncStackTrace::contextGroupId() const { return m_contextGroupId; }
415 
416 void AsyncStackTrace::setSuspendedTaskId(void* task) {
417  m_suspendedTaskId = task;
418 }
419 
420 void* AsyncStackTrace::suspendedTaskId() const { return m_suspendedTaskId; }
421 
422 uintptr_t AsyncStackTrace::store(V8Debugger* debugger,
423  std::shared_ptr<AsyncStackTrace> stack) {
424  if (stack->m_id) return stack->m_id;
425  stack->m_id = debugger->storeStackTrace(stack);
426  return stack->m_id;
427 }
428 
429 const String16& AsyncStackTrace::description() const { return m_description; }
430 
431 std::weak_ptr<AsyncStackTrace> AsyncStackTrace::parent() const {
432  return m_asyncParent;
433 }
434 
435 bool AsyncStackTrace::isEmpty() const { return m_frames.empty(); }
436 
437 } // namespace v8_inspector
STL namespace.
Local< StackFrame > GetFrame(Isolate *isolate, uint32_t index) const
Definition: api.cc:2900
V8_INLINE bool IsEmpty() const
Definition: v8.h:195
int GetFrameCount() const
Definition: api.cc:2910
static Local< StackTrace > CurrentStackTrace(Isolate *isolate, int frame_limit, StackTraceOptions options=kDetailed)
Definition: api.cc:2915
StackTraceOptions
Definition: v8.h:1691