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