Home | History | Annotate | Download | only in inspector
      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 
     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(asyncParent),
    227       m_externalParent(externalParent) {}
    228 
    229 V8StackTraceImpl::~V8StackTraceImpl() {}
    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 buildInspectorObjectCommon(debugger, m_frames, String16(),
    272                                     m_asyncParent.lock(), m_externalParent,
    273                                     m_maxAsyncDepth);
    274 }
    275 
    276 std::unique_ptr<protocol::Runtime::API::StackTrace>
    277 V8StackTraceImpl::buildInspectorObject() const {
    278   return buildInspectorObjectImpl(nullptr);
    279 }
    280 
    281 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 
    300 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 
    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 
    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 
    335 bool V8StackTraceImpl::StackFrameIterator::done() {
    336   return m_currentIt == m_currentEnd;
    337 }
    338 
    339 StackFrame* V8StackTraceImpl::StackFrameIterator::frame() {
    340   return m_currentIt->get();
    341 }
    342 
    343 // static
    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 
    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>
    401 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 
    408 int AsyncStackTrace::contextGroupId() const { return m_contextGroupId; }
    409 
    410 void AsyncStackTrace::setSuspendedTaskId(void* task) {
    411   m_suspendedTaskId = task;
    412 }
    413 
    414 void* AsyncStackTrace::suspendedTaskId() const { return m_suspendedTaskId; }
    415 
    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 
    423 const String16& AsyncStackTrace::description() const { return m_description; }
    424 
    425 std::weak_ptr<AsyncStackTrace> AsyncStackTrace::parent() const {
    426   return m_asyncParent;
    427 }
    428 
    429 bool AsyncStackTrace::isEmpty() const { return m_frames.empty(); }
    430 
    431 }  // namespace v8_inspector
    432