• 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 #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 
toFrame(v8::Local<v8::StackFrame> frame,WasmTranslation * wasmTranslation,int contextGroupId)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 
toFramesVector(v8::Local<v8::StackTrace> stackTrace,std::vector<V8StackTraceImpl::Frame> & frames,size_t maxStackSize,v8::Isolate * isolate,V8Debugger * debugger,int contextGroupId)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 
Frame()67 V8StackTraceImpl::Frame::Frame()
68     : m_functionName("undefined"),
69       m_scriptId(""),
70       m_scriptName("undefined"),
71       m_lineNumber(0),
72       m_columnNumber(0) {}
73 
Frame(const String16 & functionName,const String16 & scriptId,const String16 & scriptName,int lineNumber,int column)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 
~Frame()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>
buildInspectorObject() const93 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 
clone() const103 V8StackTraceImpl::Frame V8StackTraceImpl::Frame::clone() const {
104   return Frame(m_functionName, m_scriptId, m_scriptName, m_lineNumber,
105                m_columnNumber);
106 }
107 
108 // static
setCaptureStackTraceForUncaughtExceptions(v8::Isolate * isolate,bool capture)109 void V8StackTraceImpl::setCaptureStackTraceForUncaughtExceptions(
110     v8::Isolate* isolate, bool capture) {
111   isolate->SetCaptureStackTraceForUncaughtExceptions(
112       capture, V8StackTraceImpl::maxCallStackSizeToCapture, stackTraceOptions);
113 }
114 
115 // static
create(V8Debugger * debugger,int contextGroupId,v8::Local<v8::StackTrace> stackTrace,size_t maxStackSize,const String16 & description)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
capture(V8Debugger * debugger,int contextGroupId,size_t maxStackSize,const String16 & description)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 
cloneImpl()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 
clone()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 
V8StackTraceImpl(int contextGroupId,const String16 & description,std::vector<Frame> & frames,std::unique_ptr<V8StackTraceImpl> parent)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 
~V8StackTraceImpl()210 V8StackTraceImpl::~V8StackTraceImpl() {}
211 
setCreation(std::unique_ptr<V8StackTraceImpl> creation)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 
topSourceURL() const225 StringView V8StackTraceImpl::topSourceURL() const {
226   DCHECK(m_frames.size());
227   return toStringView(m_frames[0].m_scriptName);
228 }
229 
topLineNumber() const230 int V8StackTraceImpl::topLineNumber() const {
231   DCHECK(m_frames.size());
232   return m_frames[0].m_lineNumber;
233 }
234 
topColumnNumber() const235 int V8StackTraceImpl::topColumnNumber() const {
236   DCHECK(m_frames.size());
237   return m_frames[0].m_columnNumber;
238 }
239 
topFunctionName() const240 StringView V8StackTraceImpl::topFunctionName() const {
241   DCHECK(m_frames.size());
242   return toStringView(m_frames[0].m_functionName);
243 }
244 
topScriptId() const245 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>
buildInspectorObjectImpl() const251 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>
buildInspectorObjectForTail(V8Debugger * debugger) const271 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>
buildInspectorObject() const283 V8StackTraceImpl::buildInspectorObject() const {
284   return buildInspectorObjectImpl();
285 }
286 
toString() const287 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