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
20using v8_crdtp::SpanFrom;
21using v8_crdtp::json::ConvertCBORToJSON;
22using v8_crdtp::json::ConvertJSONToCBOR;
23
24namespace v8_inspector {
25namespace {
26
27static const char kId[] = "id";
28static const char kDebuggerId[] = "debuggerId";
29static const char kShouldPause[] = "shouldPause";
30
31static const v8::StackTrace::StackTraceOptions stackTraceOptions =
32    static_cast<v8::StackTrace::StackTraceOptions>(
33        v8::StackTrace::kDetailed |
34        v8::StackTrace::kExposeFramesAcrossSecurityOrigins);
35
36std::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
55void 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
70std::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
119V8StackTraceId::V8StackTraceId()
120    : id(0), debugger_id(internal::V8DebuggerId().pair()) {}
121
122V8StackTraceId::V8StackTraceId(uintptr_t id,
123                               const std::pair<int64_t, int64_t> debugger_id)
124    : id(id), debugger_id(debugger_id) {}
125
126V8StackTraceId::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
131V8StackTraceId::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
158bool V8StackTraceId::IsInvalid() const { return !id; }
159
160std::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
172StackFrame::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
185const String16& StackFrame::functionName() const { return m_functionName; }
186
187int StackFrame::scriptId() const { return m_scriptId; }
188
189const String16& StackFrame::sourceURL() const { return m_sourceURL; }
190
191int StackFrame::lineNumber() const { return m_lineNumber; }
192
193int StackFrame::columnNumber() const { return m_columnNumber; }
194
195std::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
219bool 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
226std::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
250std::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
269V8StackTraceImpl::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
278V8StackTraceImpl::~V8StackTraceImpl() = default;
279
280std::unique_ptr<V8StackTrace> V8StackTraceImpl::clone() {
281  return std::unique_ptr<V8StackTrace>(new V8StackTraceImpl(
282      m_frames, 0, std::shared_ptr<AsyncStackTrace>(), V8StackTraceId()));
283}
284
285StringView 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
296bool V8StackTraceImpl::isEmpty() const { return m_frames.empty(); }
297
298StringView V8StackTraceImpl::topSourceURL() const {
299  return toStringView(m_frames[0]->sourceURL());
300}
301
302int V8StackTraceImpl::topLineNumber() const {
303  return m_frames[0]->lineNumber() + 1;
304}
305
306int V8StackTraceImpl::topColumnNumber() const {
307  return m_frames[0]->columnNumber() + 1;
308}
309
310int V8StackTraceImpl::topScriptId() const { return m_frames[0]->scriptId(); }
311
312StringView V8StackTraceImpl::topFunctionName() const {
313  return toStringView(m_frames[0]->functionName());
314}
315
316std::unique_ptr<protocol::Runtime::StackTrace>
317V8StackTraceImpl::buildInspectorObjectImpl(V8Debugger* debugger) const {
318  return buildInspectorObjectImpl(debugger, m_maxAsyncDepth);
319}
320
321std::unique_ptr<protocol::Runtime::StackTrace>
322V8StackTraceImpl::buildInspectorObjectImpl(V8Debugger* debugger,
323                                           int maxAsyncDepth) const {
324  return buildInspectorObjectCommon(debugger, m_frames, String16(),
325                                    m_asyncParent.lock(), m_externalParent,
326                                    maxAsyncDepth);
327}
328
329std::unique_ptr<protocol::Runtime::API::StackTrace>
330V8StackTraceImpl::buildInspectorObject(int maxAsyncDepth) const {
331  return buildInspectorObjectImpl(nullptr,
332                                  std::min(maxAsyncDepth, m_maxAsyncDepth));
333}
334
335std::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
353bool 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
370V8StackTraceImpl::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
376void 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
387bool V8StackTraceImpl::StackFrameIterator::done() {
388  return m_currentIt == m_currentEnd;
389}
390
391StackFrame* V8StackTraceImpl::StackFrameIterator::frame() {
392  return m_currentIt->get();
393}
394
395// static
396std::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
438AsyncStackTrace::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
449std::unique_ptr<protocol::Runtime::StackTrace>
450AsyncStackTrace::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
457uintptr_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
464const String16& AsyncStackTrace::description() const { return m_description; }
465
466std::weak_ptr<AsyncStackTrace> AsyncStackTrace::parent() const {
467  return m_asyncParent;
468}
469
470bool AsyncStackTrace::isEmpty() const { return m_frames.empty(); }
471
472}  // namespace v8_inspector
473