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 "src/inspector/string-util.h"
      8 #include "src/inspector/v8-debugger-agent-impl.h"
      9 #include "src/inspector/v8-debugger.h"
     10 #include "src/inspector/v8-inspector-impl.h"
     11 
     12 #include "include/v8-version.h"
     13 
     14 namespace v8_inspector {
     15 
     16 namespace {
     17 
     18 static const v8::StackTrace::StackTraceOptions stackTraceOptions =
     19     static_cast<v8::StackTrace::StackTraceOptions>(
     20         v8::StackTrace::kLineNumber | v8::StackTrace::kColumnOffset |
     21         v8::StackTrace::kScriptId | v8::StackTrace::kScriptNameOrSourceURL |
     22         v8::StackTrace::kFunctionName);
     23 
     24 V8StackTraceImpl::Frame toFrame(v8::Local<v8::StackFrame> frame,
     25                                 WasmTranslation* wasmTranslation,
     26                                 int contextGroupId) {
     27   String16 scriptId = String16::fromInteger(frame->GetScriptId());
     28   String16 sourceName;
     29   v8::Local<v8::String> sourceNameValue(frame->GetScriptNameOrSourceURL());
     30   if (!sourceNameValue.IsEmpty())
     31     sourceName = toProtocolString(sourceNameValue);
     32 
     33   String16 functionName;
     34   v8::Local<v8::String> functionNameValue(frame->GetFunctionName());
     35   if (!functionNameValue.IsEmpty())
     36     functionName = toProtocolString(functionNameValue);
     37 
     38   int sourceLineNumber = frame->GetLineNumber() - 1;
     39   int sourceColumn = frame->GetColumn() - 1;
     40   // TODO(clemensh): Figure out a way to do this translation only right before
     41   // sending the stack trace over wire.
     42   if (wasmTranslation)
     43     wasmTranslation->TranslateWasmScriptLocationToProtocolLocation(
     44         &scriptId, &sourceLineNumber, &sourceColumn);
     45   return V8StackTraceImpl::Frame(functionName, scriptId, sourceName,
     46                                  sourceLineNumber + 1, sourceColumn + 1);
     47 }
     48 
     49 void toFramesVector(v8::Local<v8::StackTrace> stackTrace,
     50                     std::vector<V8StackTraceImpl::Frame>& frames,
     51                     size_t maxStackSize, v8::Isolate* isolate,
     52                     V8Debugger* debugger, int contextGroupId) {
     53   DCHECK(isolate->InContext());
     54   int frameCount = stackTrace->GetFrameCount();
     55   if (frameCount > static_cast<int>(maxStackSize))
     56     frameCount = static_cast<int>(maxStackSize);
     57   WasmTranslation* wasmTranslation =
     58       debugger ? debugger->wasmTranslation() : nullptr;
     59   for (int i = 0; i < frameCount; i++) {
     60     v8::Local<v8::StackFrame> stackFrame = stackTrace->GetFrame(i);
     61     frames.push_back(toFrame(stackFrame, wasmTranslation, contextGroupId));
     62   }
     63 }
     64 
     65 }  //  namespace
     66 
     67 V8StackTraceImpl::Frame::Frame()
     68     : m_functionName("undefined"),
     69       m_scriptId(""),
     70       m_scriptName("undefined"),
     71       m_lineNumber(0),
     72       m_columnNumber(0) {}
     73 
     74 V8StackTraceImpl::Frame::Frame(const String16& functionName,
     75                                const String16& scriptId,
     76                                const String16& scriptName, int lineNumber,
     77                                int column)
     78     : m_functionName(functionName),
     79       m_scriptId(scriptId),
     80       m_scriptName(scriptName),
     81       m_lineNumber(lineNumber),
     82       m_columnNumber(column) {
     83   DCHECK(m_lineNumber != v8::Message::kNoLineNumberInfo);
     84   DCHECK(m_columnNumber != v8::Message::kNoColumnInfo);
     85 }
     86 
     87 V8StackTraceImpl::Frame::~Frame() {}
     88 
     89 // buildInspectorObject() and SourceLocation's toTracedValue() should set the
     90 // same fields.
     91 // If either of them is modified, the other should be also modified.
     92 std::unique_ptr<protocol::Runtime::CallFrame>
     93 V8StackTraceImpl::Frame::buildInspectorObject() const {
     94   return protocol::Runtime::CallFrame::create()
     95       .setFunctionName(m_functionName)
     96       .setScriptId(m_scriptId)
     97       .setUrl(m_scriptName)
     98       .setLineNumber(m_lineNumber - 1)
     99       .setColumnNumber(m_columnNumber - 1)
    100       .build();
    101 }
    102 
    103 V8StackTraceImpl::Frame V8StackTraceImpl::Frame::clone() const {
    104   return Frame(m_functionName, m_scriptId, m_scriptName, m_lineNumber,
    105                m_columnNumber);
    106 }
    107 
    108 // static
    109 void V8StackTraceImpl::setCaptureStackTraceForUncaughtExceptions(
    110     v8::Isolate* isolate, bool capture) {
    111   isolate->SetCaptureStackTraceForUncaughtExceptions(
    112       capture, V8StackTraceImpl::maxCallStackSizeToCapture, stackTraceOptions);
    113 }
    114 
    115 // static
    116 std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::create(
    117     V8Debugger* debugger, int contextGroupId,
    118     v8::Local<v8::StackTrace> stackTrace, size_t maxStackSize,
    119     const String16& description) {
    120   v8::Isolate* isolate = v8::Isolate::GetCurrent();
    121   v8::HandleScope scope(isolate);
    122   std::vector<V8StackTraceImpl::Frame> frames;
    123   if (!stackTrace.IsEmpty())
    124     toFramesVector(stackTrace, frames, maxStackSize, isolate, debugger,
    125                    contextGroupId);
    126 
    127   int maxAsyncCallChainDepth = 1;
    128   V8StackTraceImpl* asyncCallChain = nullptr;
    129   if (debugger && maxStackSize > 1) {
    130     asyncCallChain = debugger->currentAsyncCallChain();
    131     maxAsyncCallChainDepth = debugger->maxAsyncCallChainDepth();
    132   }
    133   // Do not accidentally append async call chain from another group. This should
    134   // not
    135   // happen if we have proper instrumentation, but let's double-check to be
    136   // safe.
    137   if (contextGroupId && asyncCallChain && asyncCallChain->m_contextGroupId &&
    138       asyncCallChain->m_contextGroupId != contextGroupId) {
    139     asyncCallChain = nullptr;
    140     maxAsyncCallChainDepth = 1;
    141   }
    142 
    143   // Only the top stack in the chain may be empty and doesn't contain creation
    144   // stack , so ensure that second stack is non-empty (it's the top of appended
    145   // chain).
    146   if (asyncCallChain && asyncCallChain->isEmpty() &&
    147       !asyncCallChain->m_creation) {
    148     asyncCallChain = asyncCallChain->m_parent.get();
    149   }
    150 
    151   if (stackTrace.IsEmpty() && !asyncCallChain) return nullptr;
    152 
    153   std::unique_ptr<V8StackTraceImpl> result(new V8StackTraceImpl(
    154       contextGroupId, description, frames,
    155       asyncCallChain ? asyncCallChain->cloneImpl() : nullptr));
    156 
    157   // Crop to not exceed maxAsyncCallChainDepth.
    158   V8StackTraceImpl* deepest = result.get();
    159   while (deepest && maxAsyncCallChainDepth) {
    160     deepest = deepest->m_parent.get();
    161     maxAsyncCallChainDepth--;
    162   }
    163   if (deepest) deepest->m_parent.reset();
    164 
    165   return result;
    166 }
    167 
    168 // static
    169 std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::capture(
    170     V8Debugger* debugger, int contextGroupId, size_t maxStackSize,
    171     const String16& description) {
    172   v8::Isolate* isolate = v8::Isolate::GetCurrent();
    173   v8::HandleScope handleScope(isolate);
    174   v8::Local<v8::StackTrace> stackTrace;
    175   if (isolate->InContext()) {
    176     stackTrace = v8::StackTrace::CurrentStackTrace(
    177         isolate, static_cast<int>(maxStackSize), stackTraceOptions);
    178   }
    179   return V8StackTraceImpl::create(debugger, contextGroupId, stackTrace,
    180                                   maxStackSize, description);
    181 }
    182 
    183 std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::cloneImpl() {
    184   std::vector<Frame> framesCopy(m_frames);
    185   std::unique_ptr<V8StackTraceImpl> copy(
    186       new V8StackTraceImpl(m_contextGroupId, m_description, framesCopy,
    187                            m_parent ? m_parent->cloneImpl() : nullptr));
    188   if (m_creation) copy->setCreation(m_creation->cloneImpl());
    189   return copy;
    190 }
    191 
    192 std::unique_ptr<V8StackTrace> V8StackTraceImpl::clone() {
    193   std::vector<Frame> frames;
    194   for (size_t i = 0; i < m_frames.size(); i++)
    195     frames.push_back(m_frames.at(i).clone());
    196   return std::unique_ptr<V8StackTraceImpl>(
    197       new V8StackTraceImpl(m_contextGroupId, m_description, frames, nullptr));
    198 }
    199 
    200 V8StackTraceImpl::V8StackTraceImpl(int contextGroupId,
    201                                    const String16& description,
    202                                    std::vector<Frame>& frames,
    203                                    std::unique_ptr<V8StackTraceImpl> parent)
    204     : m_contextGroupId(contextGroupId),
    205       m_description(description),
    206       m_parent(std::move(parent)) {
    207   m_frames.swap(frames);
    208 }
    209 
    210 V8StackTraceImpl::~V8StackTraceImpl() {}
    211 
    212 void V8StackTraceImpl::setCreation(std::unique_ptr<V8StackTraceImpl> creation) {
    213   m_creation = std::move(creation);
    214   // When async call chain is empty but doesn't contain useful schedule stack
    215   // and parent async call chain contains creationg stack but doesn't
    216   // synchronous we can merge them together.
    217   // e.g. Promise ThenableJob.
    218   if (m_parent && isEmpty() && m_description == m_parent->m_description &&
    219       !m_parent->m_creation) {
    220     m_frames.swap(m_parent->m_frames);
    221     m_parent = std::move(m_parent->m_parent);
    222   }
    223 }
    224 
    225 StringView V8StackTraceImpl::topSourceURL() const {
    226   DCHECK(m_frames.size());
    227   return toStringView(m_frames[0].m_scriptName);
    228 }
    229 
    230 int V8StackTraceImpl::topLineNumber() const {
    231   DCHECK(m_frames.size());
    232   return m_frames[0].m_lineNumber;
    233 }
    234 
    235 int V8StackTraceImpl::topColumnNumber() const {
    236   DCHECK(m_frames.size());
    237   return m_frames[0].m_columnNumber;
    238 }
    239 
    240 StringView V8StackTraceImpl::topFunctionName() const {
    241   DCHECK(m_frames.size());
    242   return toStringView(m_frames[0].m_functionName);
    243 }
    244 
    245 StringView V8StackTraceImpl::topScriptId() const {
    246   DCHECK(m_frames.size());
    247   return toStringView(m_frames[0].m_scriptId);
    248 }
    249 
    250 std::unique_ptr<protocol::Runtime::StackTrace>
    251 V8StackTraceImpl::buildInspectorObjectImpl() const {
    252   std::unique_ptr<protocol::Array<protocol::Runtime::CallFrame>> frames =
    253       protocol::Array<protocol::Runtime::CallFrame>::create();
    254   for (size_t i = 0; i < m_frames.size(); i++)
    255     frames->addItem(m_frames.at(i).buildInspectorObject());
    256 
    257   std::unique_ptr<protocol::Runtime::StackTrace> stackTrace =
    258       protocol::Runtime::StackTrace::create()
    259           .setCallFrames(std::move(frames))
    260           .build();
    261   if (!m_description.isEmpty()) stackTrace->setDescription(m_description);
    262   if (m_parent) stackTrace->setParent(m_parent->buildInspectorObjectImpl());
    263   if (m_creation && m_creation->m_frames.size()) {
    264     stackTrace->setPromiseCreationFrame(
    265         m_creation->m_frames[0].buildInspectorObject());
    266   }
    267   return stackTrace;
    268 }
    269 
    270 std::unique_ptr<protocol::Runtime::StackTrace>
    271 V8StackTraceImpl::buildInspectorObjectForTail(V8Debugger* debugger) const {
    272   v8::HandleScope handleScope(v8::Isolate::GetCurrent());
    273   // Next call collapses possible empty stack and ensures
    274   // maxAsyncCallChainDepth.
    275   std::unique_ptr<V8StackTraceImpl> fullChain = V8StackTraceImpl::create(
    276       debugger, m_contextGroupId, v8::Local<v8::StackTrace>(),
    277       V8StackTraceImpl::maxCallStackSizeToCapture);
    278   if (!fullChain || !fullChain->m_parent) return nullptr;
    279   return fullChain->m_parent->buildInspectorObjectImpl();
    280 }
    281 
    282 std::unique_ptr<protocol::Runtime::API::StackTrace>
    283 V8StackTraceImpl::buildInspectorObject() const {
    284   return buildInspectorObjectImpl();
    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 Frame& 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()));
    298     stackTrace.append(':');
    299     stackTrace.append(String16::fromInteger(frame.columnNumber()));
    300     stackTrace.append(')');
    301   }
    302   String16 string = stackTrace.toString();
    303   return StringBufferImpl::adopt(string);
    304 }
    305 
    306 }  // namespace v8_inspector
    307