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