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#if defined(V8_OS_STARBOARD) 6#include "starboard/system.h" 7#define __builtin_abort SbSystemBreakIntoDebugger 8#endif 9 10#include "src/inspector/v8-stack-trace-impl.h" 11 12#include <algorithm> 13 14#include "../../third_party/inspector_protocol/crdtp/json.h" 15#include "src/debug/debug-interface.h" 16#include "src/inspector/v8-debugger.h" 17#include "src/inspector/v8-inspector-impl.h" 18#include "src/tracing/trace-event.h" 19 20using v8_crdtp::SpanFrom; 21using v8_crdtp::json::ConvertCBORToJSON; 22using v8_crdtp::json::ConvertJSONToCBOR; 23 24namespace v8_inspector { 25namespace { 26 27static const char kId[] = "id"; 28static const char kDebuggerId[] = "debuggerId"; 29static const char kShouldPause[] = "shouldPause"; 30 31static const v8::StackTrace::StackTraceOptions stackTraceOptions = 32 static_cast<v8::StackTrace::StackTraceOptions>( 33 v8::StackTrace::kDetailed | 34 v8::StackTrace::kExposeFramesAcrossSecurityOrigins); 35 36std::vector<std::shared_ptr<StackFrame>> toFramesVector( 37 V8Debugger* debugger, v8::Local<v8::StackTrace> v8StackTrace, 38 int maxStackSize) { 39 DCHECK(debugger->isolate()->InContext()); 40 int frameCount = std::min(v8StackTrace->GetFrameCount(), maxStackSize); 41 42 TRACE_EVENT1( 43 TRACE_DISABLED_BY_DEFAULT("v8.inspector") "," TRACE_DISABLED_BY_DEFAULT( 44 "v8.stack_trace"), 45 "toFramesVector", "frameCount", frameCount); 46 47 std::vector<std::shared_ptr<StackFrame>> frames(frameCount); 48 for (int i = 0; i < frameCount; ++i) { 49 frames[i] = 50 debugger->symbolize(v8StackTrace->GetFrame(debugger->isolate(), i)); 51 } 52 return frames; 53} 54 55void calculateAsyncChain(V8Debugger* debugger, 56 std::shared_ptr<AsyncStackTrace>* asyncParent, 57 V8StackTraceId* externalParent, int* maxAsyncDepth) { 58 *asyncParent = debugger->currentAsyncParent(); 59 *externalParent = debugger->currentExternalParent(); 60 DCHECK(externalParent->IsInvalid() || !*asyncParent); 61 if (maxAsyncDepth) *maxAsyncDepth = debugger->maxAsyncCallChainDepth(); 62 63 // Only the top stack in the chain may be empty, so ensure that second stack 64 // is non-empty (it's the top of appended chain). 65 if (*asyncParent && (*asyncParent)->isEmpty()) { 66 *asyncParent = (*asyncParent)->parent().lock(); 67 } 68} 69 70std::unique_ptr<protocol::Runtime::StackTrace> buildInspectorObjectCommon( 71 V8Debugger* debugger, 72 const std::vector<std::shared_ptr<StackFrame>>& frames, 73 const String16& description, 74 const std::shared_ptr<AsyncStackTrace>& asyncParent, 75 const V8StackTraceId& externalParent, int maxAsyncDepth) { 76 if (asyncParent && frames.empty() && 77 description == asyncParent->description()) { 78 return asyncParent->buildInspectorObject(debugger, maxAsyncDepth); 79 } 80 81 auto inspectorFrames = 82 std::make_unique<protocol::Array<protocol::Runtime::CallFrame>>(); 83 for (const std::shared_ptr<StackFrame>& frame : frames) { 84 V8InspectorClient* client = nullptr; 85 if (debugger && debugger->inspector()) 86 client = debugger->inspector()->client(); 87 inspectorFrames->emplace_back(frame->buildInspectorObject(client)); 88 } 89 std::unique_ptr<protocol::Runtime::StackTrace> stackTrace = 90 protocol::Runtime::StackTrace::create() 91 .setCallFrames(std::move(inspectorFrames)) 92 .build(); 93 if (!description.isEmpty()) stackTrace->setDescription(description); 94 if (asyncParent) { 95 if (maxAsyncDepth > 0) { 96 stackTrace->setParent( 97 asyncParent->buildInspectorObject(debugger, maxAsyncDepth - 1)); 98 } else if (debugger) { 99 stackTrace->setParentId( 100 protocol::Runtime::StackTraceId::create() 101 .setId(stackTraceIdToString( 102 AsyncStackTrace::store(debugger, asyncParent))) 103 .build()); 104 } 105 } 106 if (!externalParent.IsInvalid()) { 107 stackTrace->setParentId( 108 protocol::Runtime::StackTraceId::create() 109 .setId(stackTraceIdToString(externalParent.id)) 110 .setDebuggerId( 111 internal::V8DebuggerId(externalParent.debugger_id).toString()) 112 .build()); 113 } 114 return stackTrace; 115} 116 117} // namespace 118 119V8StackTraceId::V8StackTraceId() 120 : id(0), debugger_id(internal::V8DebuggerId().pair()) {} 121 122V8StackTraceId::V8StackTraceId(uintptr_t id, 123 const std::pair<int64_t, int64_t> debugger_id) 124 : id(id), debugger_id(debugger_id) {} 125 126V8StackTraceId::V8StackTraceId(uintptr_t id, 127 const std::pair<int64_t, int64_t> debugger_id, 128 bool should_pause) 129 : id(id), debugger_id(debugger_id), should_pause(should_pause) {} 130 131V8StackTraceId::V8StackTraceId(StringView json) 132 : id(0), debugger_id(internal::V8DebuggerId().pair()) { 133 if (json.length() == 0) return; 134 std::vector<uint8_t> cbor; 135 if (json.is8Bit()) { 136 ConvertJSONToCBOR( 137 v8_crdtp::span<uint8_t>(json.characters8(), json.length()), &cbor); 138 } else { 139 ConvertJSONToCBOR( 140 v8_crdtp::span<uint16_t>(json.characters16(), json.length()), &cbor); 141 } 142 auto dict = protocol::DictionaryValue::cast( 143 protocol::Value::parseBinary(cbor.data(), cbor.size())); 144 if (!dict) return; 145 String16 s; 146 if (!dict->getString(kId, &s)) return; 147 bool isOk = false; 148 int64_t parsedId = s.toInteger64(&isOk); 149 if (!isOk || !parsedId) return; 150 if (!dict->getString(kDebuggerId, &s)) return; 151 internal::V8DebuggerId debuggerId(s); 152 if (!debuggerId.isValid()) return; 153 if (!dict->getBoolean(kShouldPause, &should_pause)) return; 154 id = parsedId; 155 debugger_id = debuggerId.pair(); 156} 157 158bool V8StackTraceId::IsInvalid() const { return !id; } 159 160std::unique_ptr<StringBuffer> V8StackTraceId::ToString() { 161 if (IsInvalid()) return nullptr; 162 auto dict = protocol::DictionaryValue::create(); 163 dict->setString(kId, String16::fromInteger64(id)); 164 dict->setString(kDebuggerId, internal::V8DebuggerId(debugger_id).toString()); 165 dict->setBoolean(kShouldPause, should_pause); 166 std::vector<uint8_t> json; 167 v8_crdtp::json::ConvertCBORToJSON(v8_crdtp::SpanFrom(dict->Serialize()), 168 &json); 169 return StringBufferFrom(std::move(json)); 170} 171 172StackFrame::StackFrame(String16&& functionName, int scriptId, 173 String16&& sourceURL, int lineNumber, int columnNumber, 174 bool hasSourceURLComment) 175 : m_functionName(std::move(functionName)), 176 m_scriptId(scriptId), 177 m_sourceURL(std::move(sourceURL)), 178 m_lineNumber(lineNumber), 179 m_columnNumber(columnNumber), 180 m_hasSourceURLComment(hasSourceURLComment) { 181 DCHECK_NE(v8::Message::kNoLineNumberInfo, m_lineNumber + 1); 182 DCHECK_NE(v8::Message::kNoColumnInfo, m_columnNumber + 1); 183} 184 185const String16& StackFrame::functionName() const { return m_functionName; } 186 187int StackFrame::scriptId() const { return m_scriptId; } 188 189const String16& StackFrame::sourceURL() const { return m_sourceURL; } 190 191int StackFrame::lineNumber() const { return m_lineNumber; } 192 193int StackFrame::columnNumber() const { return m_columnNumber; } 194 195std::unique_ptr<protocol::Runtime::CallFrame> StackFrame::buildInspectorObject( 196 V8InspectorClient* client) const { 197 String16 frameUrl; 198 const char* dataURIPrefix = "data:"; 199 if (m_sourceURL.substring(0, strlen(dataURIPrefix)) != dataURIPrefix) { 200 frameUrl = m_sourceURL; 201 } 202 203 if (client && !m_hasSourceURLComment && frameUrl.length() > 0) { 204 std::unique_ptr<StringBuffer> url = 205 client->resourceNameToUrl(toStringView(m_sourceURL)); 206 if (url) { 207 frameUrl = toString16(url->string()); 208 } 209 } 210 return protocol::Runtime::CallFrame::create() 211 .setFunctionName(m_functionName) 212 .setScriptId(String16::fromInteger(m_scriptId)) 213 .setUrl(frameUrl) 214 .setLineNumber(m_lineNumber) 215 .setColumnNumber(m_columnNumber) 216 .build(); 217} 218 219bool StackFrame::isEqual(StackFrame* frame) const { 220 return m_scriptId == frame->m_scriptId && 221 m_lineNumber == frame->m_lineNumber && 222 m_columnNumber == frame->m_columnNumber; 223} 224 225// static 226std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::create( 227 V8Debugger* debugger, v8::Local<v8::StackTrace> v8StackTrace, 228 int maxStackSize) { 229 DCHECK(debugger); 230 231 v8::Isolate* isolate = debugger->isolate(); 232 v8::HandleScope scope(isolate); 233 234 std::vector<std::shared_ptr<StackFrame>> frames; 235 if (!v8StackTrace.IsEmpty() && v8StackTrace->GetFrameCount()) { 236 frames = toFramesVector(debugger, v8StackTrace, maxStackSize); 237 } 238 239 int maxAsyncDepth = 0; 240 std::shared_ptr<AsyncStackTrace> asyncParent; 241 V8StackTraceId externalParent; 242 calculateAsyncChain(debugger, &asyncParent, &externalParent, &maxAsyncDepth); 243 if (frames.empty() && !asyncParent && externalParent.IsInvalid()) 244 return nullptr; 245 return std::unique_ptr<V8StackTraceImpl>(new V8StackTraceImpl( 246 std::move(frames), maxAsyncDepth, asyncParent, externalParent)); 247} 248 249// static 250std::unique_ptr<V8StackTraceImpl> V8StackTraceImpl::capture( 251 V8Debugger* debugger, int maxStackSize) { 252 DCHECK(debugger); 253 254 TRACE_EVENT1( 255 TRACE_DISABLED_BY_DEFAULT("v8.inspector") "," TRACE_DISABLED_BY_DEFAULT( 256 "v8.stack_trace"), 257 "V8StackTraceImpl::capture", "maxFrameCount", maxStackSize); 258 259 v8::Isolate* isolate = debugger->isolate(); 260 v8::HandleScope handleScope(isolate); 261 v8::Local<v8::StackTrace> v8StackTrace; 262 if (isolate->InContext()) { 263 v8StackTrace = v8::StackTrace::CurrentStackTrace(isolate, maxStackSize, 264 stackTraceOptions); 265 } 266 return V8StackTraceImpl::create(debugger, v8StackTrace, maxStackSize); 267} 268 269V8StackTraceImpl::V8StackTraceImpl( 270 std::vector<std::shared_ptr<StackFrame>> frames, int maxAsyncDepth, 271 std::shared_ptr<AsyncStackTrace> asyncParent, 272 const V8StackTraceId& externalParent) 273 : m_frames(std::move(frames)), 274 m_maxAsyncDepth(maxAsyncDepth), 275 m_asyncParent(std::move(asyncParent)), 276 m_externalParent(externalParent) {} 277 278V8StackTraceImpl::~V8StackTraceImpl() = default; 279 280std::unique_ptr<V8StackTrace> V8StackTraceImpl::clone() { 281 return std::unique_ptr<V8StackTrace>(new V8StackTraceImpl( 282 m_frames, 0, std::shared_ptr<AsyncStackTrace>(), V8StackTraceId())); 283} 284 285StringView V8StackTraceImpl::firstNonEmptySourceURL() const { 286 StackFrameIterator current(this); 287 while (!current.done()) { 288 if (current.frame()->sourceURL().length()) { 289 return toStringView(current.frame()->sourceURL()); 290 } 291 current.next(); 292 } 293 return StringView(); 294} 295 296bool V8StackTraceImpl::isEmpty() const { return m_frames.empty(); } 297 298StringView V8StackTraceImpl::topSourceURL() const { 299 return toStringView(m_frames[0]->sourceURL()); 300} 301 302int V8StackTraceImpl::topLineNumber() const { 303 return m_frames[0]->lineNumber() + 1; 304} 305 306int V8StackTraceImpl::topColumnNumber() const { 307 return m_frames[0]->columnNumber() + 1; 308} 309 310int V8StackTraceImpl::topScriptId() const { return m_frames[0]->scriptId(); } 311 312StringView V8StackTraceImpl::topFunctionName() const { 313 return toStringView(m_frames[0]->functionName()); 314} 315 316std::unique_ptr<protocol::Runtime::StackTrace> 317V8StackTraceImpl::buildInspectorObjectImpl(V8Debugger* debugger) const { 318 return buildInspectorObjectImpl(debugger, m_maxAsyncDepth); 319} 320 321std::unique_ptr<protocol::Runtime::StackTrace> 322V8StackTraceImpl::buildInspectorObjectImpl(V8Debugger* debugger, 323 int maxAsyncDepth) const { 324 return buildInspectorObjectCommon(debugger, m_frames, String16(), 325 m_asyncParent.lock(), m_externalParent, 326 maxAsyncDepth); 327} 328 329std::unique_ptr<protocol::Runtime::API::StackTrace> 330V8StackTraceImpl::buildInspectorObject(int maxAsyncDepth) const { 331 return buildInspectorObjectImpl(nullptr, 332 std::min(maxAsyncDepth, m_maxAsyncDepth)); 333} 334 335std::unique_ptr<StringBuffer> V8StackTraceImpl::toString() const { 336 String16Builder stackTrace; 337 for (size_t i = 0; i < m_frames.size(); ++i) { 338 const StackFrame& frame = *m_frames[i]; 339 stackTrace.append("\n at " + (frame.functionName().length() 340 ? frame.functionName() 341 : "(anonymous function)")); 342 stackTrace.append(" ("); 343 stackTrace.append(frame.sourceURL()); 344 stackTrace.append(':'); 345 stackTrace.append(String16::fromInteger(frame.lineNumber() + 1)); 346 stackTrace.append(':'); 347 stackTrace.append(String16::fromInteger(frame.columnNumber() + 1)); 348 stackTrace.append(')'); 349 } 350 return StringBufferFrom(stackTrace.toString()); 351} 352 353bool V8StackTraceImpl::isEqualIgnoringTopFrame( 354 V8StackTraceImpl* stackTrace) const { 355 StackFrameIterator current(this); 356 StackFrameIterator target(stackTrace); 357 358 current.next(); 359 target.next(); 360 while (!current.done() && !target.done()) { 361 if (!current.frame()->isEqual(target.frame())) { 362 return false; 363 } 364 current.next(); 365 target.next(); 366 } 367 return current.done() == target.done(); 368} 369 370V8StackTraceImpl::StackFrameIterator::StackFrameIterator( 371 const V8StackTraceImpl* stackTrace) 372 : m_currentIt(stackTrace->m_frames.begin()), 373 m_currentEnd(stackTrace->m_frames.end()), 374 m_parent(stackTrace->m_asyncParent.lock().get()) {} 375 376void V8StackTraceImpl::StackFrameIterator::next() { 377 if (m_currentIt == m_currentEnd) return; 378 ++m_currentIt; 379 while (m_currentIt == m_currentEnd && m_parent) { 380 const std::vector<std::shared_ptr<StackFrame>>& frames = m_parent->frames(); 381 m_currentIt = frames.begin(); 382 m_currentEnd = frames.end(); 383 m_parent = m_parent->parent().lock().get(); 384 } 385} 386 387bool V8StackTraceImpl::StackFrameIterator::done() { 388 return m_currentIt == m_currentEnd; 389} 390 391StackFrame* V8StackTraceImpl::StackFrameIterator::frame() { 392 return m_currentIt->get(); 393} 394 395// static 396std::shared_ptr<AsyncStackTrace> AsyncStackTrace::capture( 397 V8Debugger* debugger, const String16& description, bool skipTopFrame) { 398 DCHECK(debugger); 399 400 int maxStackSize = debugger->maxCallStackSizeToCapture(); 401 TRACE_EVENT1( 402 TRACE_DISABLED_BY_DEFAULT("v8.inspector") "," TRACE_DISABLED_BY_DEFAULT( 403 "v8.stack_trace"), 404 "AsyncStackTrace::capture", "maxFrameCount", maxStackSize); 405 406 v8::Isolate* isolate = debugger->isolate(); 407 v8::HandleScope handleScope(isolate); 408 409 std::vector<std::shared_ptr<StackFrame>> frames; 410 if (isolate->InContext()) { 411 v8::Local<v8::StackTrace> v8StackTrace = v8::StackTrace::CurrentStackTrace( 412 isolate, maxStackSize, stackTraceOptions); 413 frames = toFramesVector(debugger, v8StackTrace, maxStackSize); 414 if (skipTopFrame && !frames.empty()) { 415 frames.erase(frames.begin()); 416 } 417 } 418 419 std::shared_ptr<AsyncStackTrace> asyncParent; 420 V8StackTraceId externalParent; 421 calculateAsyncChain(debugger, &asyncParent, &externalParent, nullptr); 422 423 if (frames.empty() && !asyncParent && externalParent.IsInvalid()) 424 return nullptr; 425 426 // When async call chain is empty but doesn't contain useful schedule stack 427 // but doesn't synchronous we can merge them together. e.g. Promise 428 // ThenableJob. 429 if (asyncParent && frames.empty() && 430 (asyncParent->m_description == description || description.isEmpty())) { 431 return asyncParent; 432 } 433 434 return std::shared_ptr<AsyncStackTrace>(new AsyncStackTrace( 435 description, std::move(frames), asyncParent, externalParent)); 436} 437 438AsyncStackTrace::AsyncStackTrace( 439 const String16& description, 440 std::vector<std::shared_ptr<StackFrame>> frames, 441 std::shared_ptr<AsyncStackTrace> asyncParent, 442 const V8StackTraceId& externalParent) 443 : m_id(0), 444 m_description(description), 445 m_frames(std::move(frames)), 446 m_asyncParent(std::move(asyncParent)), 447 m_externalParent(externalParent) {} 448 449std::unique_ptr<protocol::Runtime::StackTrace> 450AsyncStackTrace::buildInspectorObject(V8Debugger* debugger, 451 int maxAsyncDepth) const { 452 return buildInspectorObjectCommon(debugger, m_frames, m_description, 453 m_asyncParent.lock(), m_externalParent, 454 maxAsyncDepth); 455} 456 457uintptr_t AsyncStackTrace::store(V8Debugger* debugger, 458 std::shared_ptr<AsyncStackTrace> stack) { 459 if (stack->m_id) return stack->m_id; 460 stack->m_id = debugger->storeStackTrace(stack); 461 return stack->m_id; 462} 463 464const String16& AsyncStackTrace::description() const { return m_description; } 465 466std::weak_ptr<AsyncStackTrace> AsyncStackTrace::parent() const { 467 return m_asyncParent; 468} 469 470bool AsyncStackTrace::isEmpty() const { return m_frames.empty(); } 471 472} // namespace v8_inspector 473