• 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 #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 
20 using v8_crdtp::SpanFrom;
21 using v8_crdtp::json::ConvertCBORToJSON;
22 using v8_crdtp::json::ConvertJSONToCBOR;
23 
24 namespace v8_inspector {
25 namespace {
26 
27 static const char kId[] = "id";
28 static const char kDebuggerId[] = "debuggerId";
29 static const char kShouldPause[] = "shouldPause";
30 
31 static const v8::StackTrace::StackTraceOptions stackTraceOptions =
32     static_cast<v8::StackTrace::StackTraceOptions>(
33         v8::StackTrace::kDetailed |
34         v8::StackTrace::kExposeFramesAcrossSecurityOrigins);
35 
toFramesVector(V8Debugger * debugger,v8::Local<v8::StackTrace> v8StackTrace,int maxStackSize)36 std::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 
calculateAsyncChain(V8Debugger * debugger,std::shared_ptr<AsyncStackTrace> * asyncParent,V8StackTraceId * externalParent,int * maxAsyncDepth)55 void 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 
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)70 std::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 
V8StackTraceId()119 V8StackTraceId::V8StackTraceId()
120     : id(0), debugger_id(internal::V8DebuggerId().pair()) {}
121 
V8StackTraceId(uintptr_t id,const std::pair<int64_t,int64_t> debugger_id)122 V8StackTraceId::V8StackTraceId(uintptr_t id,
123                                const std::pair<int64_t, int64_t> debugger_id)
124     : id(id), debugger_id(debugger_id) {}
125 
V8StackTraceId(uintptr_t id,const std::pair<int64_t,int64_t> debugger_id,bool should_pause)126 V8StackTraceId::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 
V8StackTraceId(StringView json)131 V8StackTraceId::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 
IsInvalid() const158 bool V8StackTraceId::IsInvalid() const { return !id; }
159 
ToString()160 std::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 
StackFrame(String16 && functionName,int scriptId,String16 && sourceURL,int lineNumber,int columnNumber,bool hasSourceURLComment)172 StackFrame::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 
functionName() const185 const String16& StackFrame::functionName() const { return m_functionName; }
186 
scriptId() const187 int StackFrame::scriptId() const { return m_scriptId; }
188 
sourceURL() const189 const String16& StackFrame::sourceURL() const { return m_sourceURL; }
190 
lineNumber() const191 int StackFrame::lineNumber() const { return m_lineNumber; }
192 
columnNumber() const193 int StackFrame::columnNumber() const { return m_columnNumber; }
194 
buildInspectorObject(V8InspectorClient * client) const195 std::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 
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
create(V8Debugger * debugger,v8::Local<v8::StackTrace> v8StackTrace,int maxStackSize)226 std::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
capture(V8Debugger * debugger,int maxStackSize)250 std::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 
V8StackTraceImpl(std::vector<std::shared_ptr<StackFrame>> frames,int maxAsyncDepth,std::shared_ptr<AsyncStackTrace> asyncParent,const V8StackTraceId & externalParent)269 V8StackTraceImpl::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 
278 V8StackTraceImpl::~V8StackTraceImpl() = default;
279 
clone()280 std::unique_ptr<V8StackTrace> V8StackTraceImpl::clone() {
281   return std::unique_ptr<V8StackTrace>(new V8StackTraceImpl(
282       m_frames, 0, std::shared_ptr<AsyncStackTrace>(), V8StackTraceId()));
283 }
284 
firstNonEmptySourceURL() const285 StringView 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 
isEmpty() const296 bool V8StackTraceImpl::isEmpty() const { return m_frames.empty(); }
297 
topSourceURL() const298 StringView V8StackTraceImpl::topSourceURL() const {
299   return toStringView(m_frames[0]->sourceURL());
300 }
301 
topLineNumber() const302 int V8StackTraceImpl::topLineNumber() const {
303   return m_frames[0]->lineNumber() + 1;
304 }
305 
topColumnNumber() const306 int V8StackTraceImpl::topColumnNumber() const {
307   return m_frames[0]->columnNumber() + 1;
308 }
309 
topScriptId() const310 int V8StackTraceImpl::topScriptId() const { return m_frames[0]->scriptId(); }
311 
topFunctionName() const312 StringView V8StackTraceImpl::topFunctionName() const {
313   return toStringView(m_frames[0]->functionName());
314 }
315 
316 std::unique_ptr<protocol::Runtime::StackTrace>
buildInspectorObjectImpl(V8Debugger * debugger) const317 V8StackTraceImpl::buildInspectorObjectImpl(V8Debugger* debugger) const {
318   return buildInspectorObjectImpl(debugger, m_maxAsyncDepth);
319 }
320 
321 std::unique_ptr<protocol::Runtime::StackTrace>
buildInspectorObjectImpl(V8Debugger * debugger,int maxAsyncDepth) const322 V8StackTraceImpl::buildInspectorObjectImpl(V8Debugger* debugger,
323                                            int maxAsyncDepth) const {
324   return buildInspectorObjectCommon(debugger, m_frames, String16(),
325                                     m_asyncParent.lock(), m_externalParent,
326                                     maxAsyncDepth);
327 }
328 
329 std::unique_ptr<protocol::Runtime::API::StackTrace>
buildInspectorObject(int maxAsyncDepth) const330 V8StackTraceImpl::buildInspectorObject(int maxAsyncDepth) const {
331   return buildInspectorObjectImpl(nullptr,
332                                   std::min(maxAsyncDepth, m_maxAsyncDepth));
333 }
334 
toString() const335 std::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 
isEqualIgnoringTopFrame(V8StackTraceImpl * stackTrace) const353 bool 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 
StackFrameIterator(const V8StackTraceImpl * stackTrace)370 V8StackTraceImpl::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 
next()376 void 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 
done()387 bool V8StackTraceImpl::StackFrameIterator::done() {
388   return m_currentIt == m_currentEnd;
389 }
390 
frame()391 StackFrame* V8StackTraceImpl::StackFrameIterator::frame() {
392   return m_currentIt->get();
393 }
394 
395 // static
capture(V8Debugger * debugger,const String16 & description,bool skipTopFrame)396 std::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 
AsyncStackTrace(const String16 & description,std::vector<std::shared_ptr<StackFrame>> frames,std::shared_ptr<AsyncStackTrace> asyncParent,const V8StackTraceId & externalParent)438 AsyncStackTrace::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 
449 std::unique_ptr<protocol::Runtime::StackTrace>
buildInspectorObject(V8Debugger * debugger,int maxAsyncDepth) const450 AsyncStackTrace::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 
store(V8Debugger * debugger,std::shared_ptr<AsyncStackTrace> stack)457 uintptr_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 
description() const464 const String16& AsyncStackTrace::description() const { return m_description; }
465 
parent() const466 std::weak_ptr<AsyncStackTrace> AsyncStackTrace::parent() const {
467   return m_asyncParent;
468 }
469 
isEmpty() const470 bool AsyncStackTrace::isEmpty() const { return m_frames.empty(); }
471 
472 }  // namespace v8_inspector
473