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