ExecutionTracer.cpp (46561B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "debugger/ExecutionTracer.h" 8 9 #include "mozilla/FloatingPoint.h" // IsPositiveZero 10 #include "mozilla/Printf.h" // SprintfBuf 11 12 #include "builtin/BigInt.h" // BigIntObject 13 #include "builtin/MapObject.h" // MapObject, SetObject 14 #include "builtin/Symbol.h" // SymbolObject 15 16 #include "debugger/Frame.h" // DebuggerFrameType 17 18 #include "vm/BooleanObject.h" // BooleanObject 19 #include "vm/ErrorObject.h" 20 #include "vm/NumberObject.h" // NumberObject 21 #include "vm/ObjectOperations.h" // DefineDataElement 22 #include "vm/StringObject.h" // StringObject 23 #include "vm/Time.h" 24 25 #include "debugger/Debugger-inl.h" 26 #include "vm/ErrorObject-inl.h" // ErrorObject.fileName,lineNumber,... 27 #include "vm/Stack-inl.h" 28 29 using namespace js; 30 31 MOZ_RUNINIT mozilla::Vector<ExecutionTracer*> ExecutionTracer::globalInstances; 32 MOZ_RUNINIT Mutex 33 ExecutionTracer::globalInstanceLock(mutexid::ExecutionTracerGlobalLock); 34 35 // This is a magic value we write as the last 64 bits of a FunctionEnter event 36 // in ExecutionTracer::inlineData_. It just means that the actual argc for the 37 // function call was 0. If the last 64 bits are not this value, they instead 38 // represent the index into ExecutionTracer::valueData_ at which we can find 39 // the actual argc count as well as the list of ValueSummaries for the argument 40 // values. Having this magic value allows us to avoid needing to write a 32-bit 41 // `0` to ExecutionTracer::valueData_ in the common case where a function is 42 // called with no arguments. This value is essentially the 64-bit mirror to 43 // JS::ExecutionTrace::ZERO_ARGUMENTS_MAGIC. 44 const uint64_t IN_BUFFER_ZERO_ARGUMENTS_MAGIC = 0xFFFFFFFFFFFFFFFFllu; 45 46 static JS::ExecutionTrace::ImplementationType GetImplementation( 47 AbstractFramePtr frame) { 48 if (frame.isBaselineFrame()) { 49 return JS::ExecutionTrace::ImplementationType::Baseline; 50 } 51 52 if (frame.isRematerializedFrame()) { 53 return JS::ExecutionTrace::ImplementationType::Ion; 54 } 55 56 if (frame.isWasmDebugFrame()) { 57 return JS::ExecutionTrace::ImplementationType::Wasm; 58 } 59 60 return JS::ExecutionTrace::ImplementationType::Interpreter; 61 } 62 63 static DebuggerFrameType GetFrameType(AbstractFramePtr frame) { 64 // Indirect eval frames are both isGlobalFrame() and isEvalFrame(), so the 65 // order of checks here is significant. 66 if (frame.isEvalFrame()) { 67 return DebuggerFrameType::Eval; 68 } 69 70 if (frame.isGlobalFrame()) { 71 return DebuggerFrameType::Global; 72 } 73 74 if (frame.isFunctionFrame()) { 75 return DebuggerFrameType::Call; 76 } 77 78 if (frame.isModuleFrame()) { 79 return DebuggerFrameType::Module; 80 } 81 82 if (frame.isWasmDebugFrame()) { 83 return DebuggerFrameType::WasmCall; 84 } 85 86 MOZ_CRASH("Unknown frame type"); 87 } 88 89 [[nodiscard]] static bool GetFunctionName(JSContext* cx, 90 JS::Handle<JSFunction*> fun, 91 JS::MutableHandle<JSAtom*> result) { 92 if (!fun->getDisplayAtom(cx, result)) { 93 return false; 94 } 95 96 if (result) { 97 cx->markAtom(result); 98 } 99 return true; 100 } 101 102 static double GetNowMilliseconds() { 103 return (mozilla::TimeStamp::Now() - mozilla::TimeStamp::ProcessCreation()) 104 .ToMilliseconds(); 105 } 106 107 void ExecutionTracer::handleError(JSContext* cx) { 108 inlineData_.beginWritingEntry(); 109 inlineData_.write(uint8_t(InlineEntryType::Error)); 110 inlineData_.finishWritingEntry(); 111 cx->clearPendingException(); 112 cx->suspendExecutionTracing(); 113 } 114 115 void ExecutionTracer::writeScriptUrl(ScriptSource* scriptSource) { 116 outOfLineData_.beginWritingEntry(); 117 outOfLineData_.write(uint8_t(OutOfLineEntryType::ScriptURL)); 118 outOfLineData_.write(scriptSource->id()); 119 120 if (scriptSource->hasDisplayURL()) { 121 outOfLineData_.writeCString<char16_t, JS::TracerStringEncoding::TwoByte>( 122 scriptSource->displayURL()); 123 } else { 124 const char* filename = 125 scriptSource->filename() ? scriptSource->filename() : ""; 126 outOfLineData_.writeCString<char, JS::TracerStringEncoding::UTF8>(filename); 127 } 128 outOfLineData_.finishWritingEntry(); 129 } 130 131 bool ExecutionTracer::writeAtom(JSContext* cx, JS::Handle<JSAtom*> atom, 132 uint32_t id) { 133 outOfLineData_.beginWritingEntry(); 134 outOfLineData_.write(uint8_t(OutOfLineEntryType::Atom)); 135 outOfLineData_.write(id); 136 137 if (!atom) { 138 outOfLineData_.writeEmptyString(); 139 } else { 140 if (!outOfLineData_.writeString(cx, atom)) { 141 return false; 142 } 143 } 144 outOfLineData_.finishWritingEntry(); 145 return true; 146 } 147 148 bool ExecutionTracer::writeFunctionFrame(JSContext* cx, 149 AbstractFramePtr frame) { 150 JS::Rooted<JSFunction*> fn(cx, frame.callee()); 151 TracingCaches& caches = cx->caches().tracingCaches; 152 if (fn->baseScript()) { 153 uint32_t scriptSourceId = fn->baseScript()->scriptSource()->id(); 154 TracingCaches::GetOrPutResult scriptSourceRes = 155 caches.putScriptSourceIfMissing(scriptSourceId); 156 if (scriptSourceRes == TracingCaches::GetOrPutResult::OOM) { 157 ReportOutOfMemory(cx); 158 return false; 159 } 160 if (scriptSourceRes == TracingCaches::GetOrPutResult::NewlyAdded) { 161 writeScriptUrl(fn->baseScript()->scriptSource()); 162 } 163 inlineData_.write(fn->baseScript()->lineno()); 164 inlineData_.write(fn->baseScript()->column().oneOriginValue()); 165 inlineData_.write(scriptSourceId); 166 inlineData_.write( 167 fn->baseScript()->realm()->creationOptions().profilerRealmID()); 168 } else { 169 // In the case of no baseScript, we just fill it out with 0s. 0 is an 170 // invalid script source ID, so it is distinguishable from a real one 171 inlineData_.write(uint32_t(0)); // line number 172 inlineData_.write(uint32_t(0)); // column 173 inlineData_.write(uint32_t(0)); // script source id 174 inlineData_.write(uint64_t(0)); // realm id 175 } 176 177 JS::Rooted<JSAtom*> functionName(cx); 178 if (!GetFunctionName(cx, fn, &functionName)) { 179 return false; 180 } 181 uint32_t functionNameId = 0; 182 TracingCaches::GetOrPutResult fnNameRes = 183 caches.getOrPutAtom(functionName, &functionNameId); 184 if (fnNameRes == TracingCaches::GetOrPutResult::OOM) { 185 ReportOutOfMemory(cx); 186 return false; 187 } 188 if (fnNameRes == TracingCaches::GetOrPutResult::NewlyAdded) { 189 if (!writeAtom(cx, functionName, functionNameId)) { 190 // It's worth noting here that this will leave the caches out of sync 191 // with what has actually been written into the out of line data. 192 // This is a normal and allowed situation for the tracer, so we have 193 // no special handling here for it. However, if we ever want to make 194 // a stronger guarantee in the future, we need to revisit this. 195 return false; 196 } 197 } 198 199 inlineData_.write(functionNameId); 200 inlineData_.write(uint8_t(GetImplementation(frame))); 201 inlineData_.write(GetNowMilliseconds()); 202 return true; 203 } 204 205 void ExecutionTracer::onEnterFrame(JSContext* cx, AbstractFramePtr frame) { 206 LockGuard<Mutex> guard(bufferLock_); 207 208 DebuggerFrameType type = GetFrameType(frame); 209 if (type == DebuggerFrameType::Call) { 210 if (frame.isFunctionFrame() && !frame.callee()->isSelfHostedBuiltin()) { 211 inlineData_.beginWritingEntry(); 212 inlineData_.write(uint8_t(InlineEntryType::StackFunctionEnter)); 213 if (!writeFunctionFrame(cx, frame)) { 214 handleError(cx); 215 return; 216 } 217 218 if (frame.numActualArgs() == 0) { 219 inlineData_.write(IN_BUFFER_ZERO_ARGUMENTS_MAGIC); 220 } else { 221 uint64_t argumentsIndex; 222 if (!valueSummaries_.writeArguments(cx, frame, &argumentsIndex)) { 223 handleError(cx); 224 return; 225 } 226 inlineData_.write(argumentsIndex); 227 } 228 229 inlineData_.finishWritingEntry(); 230 } 231 } 232 } 233 234 void ExecutionTracer::onLeaveFrame(JSContext* cx, AbstractFramePtr frame) { 235 LockGuard<Mutex> guard(bufferLock_); 236 237 DebuggerFrameType type = GetFrameType(frame); 238 if (type == DebuggerFrameType::Call) { 239 if (frame.isFunctionFrame() && !frame.callee()->isSelfHostedBuiltin()) { 240 inlineData_.beginWritingEntry(); 241 inlineData_.write(uint8_t(InlineEntryType::StackFunctionLeave)); 242 if (!writeFunctionFrame(cx, frame)) { 243 handleError(cx); 244 return; 245 } 246 inlineData_.finishWritingEntry(); 247 } 248 } 249 } 250 251 template <typename CharType, JS::TracerStringEncoding Encoding> 252 void ExecutionTracer::onEnterLabel(const CharType* eventType) { 253 LockGuard<Mutex> guard(bufferLock_); 254 255 inlineData_.beginWritingEntry(); 256 inlineData_.write(uint8_t(InlineEntryType::LabelEnter)); 257 inlineData_.writeCString<CharType, Encoding>(eventType); 258 inlineData_.write(GetNowMilliseconds()); 259 inlineData_.finishWritingEntry(); 260 } 261 262 template <typename CharType, JS::TracerStringEncoding Encoding> 263 void ExecutionTracer::onLeaveLabel(const CharType* eventType) { 264 LockGuard<Mutex> guard(bufferLock_); 265 266 inlineData_.beginWritingEntry(); 267 inlineData_.write(uint8_t(InlineEntryType::LabelLeave)); 268 inlineData_.writeCString<CharType, Encoding>(eventType); 269 inlineData_.write(GetNowMilliseconds()); 270 inlineData_.finishWritingEntry(); 271 } 272 273 bool ExecutionTracer::readFunctionFrame( 274 JS::ExecutionTrace::EventKind kind, 275 JS::ExecutionTrace::TracedEvent& event) { 276 MOZ_ASSERT(kind == JS::ExecutionTrace::EventKind::FunctionEnter || 277 kind == JS::ExecutionTrace::EventKind::FunctionLeave); 278 279 event.kind = kind; 280 281 uint8_t implementation; 282 inlineData_.read(&event.functionEvent.lineNumber); 283 inlineData_.read(&event.functionEvent.column); 284 inlineData_.read(&event.functionEvent.scriptId); 285 inlineData_.read(&event.functionEvent.realmID); 286 inlineData_.read(&event.functionEvent.functionNameId); 287 inlineData_.read(&implementation); 288 inlineData_.read(&event.time); 289 290 event.functionEvent.implementation = 291 JS::ExecutionTrace::ImplementationType(implementation); 292 293 if (kind == JS::ExecutionTrace::EventKind::FunctionEnter) { 294 uint64_t argumentsIndex; 295 inlineData_.read(&argumentsIndex); 296 if (argumentsIndex == IN_BUFFER_ZERO_ARGUMENTS_MAGIC) { 297 event.functionEvent.values = JS::ExecutionTrace::ZERO_ARGUMENTS_MAGIC; 298 } else { 299 event.functionEvent.values = 300 valueSummaries_.getOutputBufferIndex(argumentsIndex); 301 } 302 } else { 303 event.functionEvent.values = JS::ExecutionTrace::FUNCTION_LEAVE_VALUES; 304 } 305 306 return true; 307 } 308 309 bool ExecutionTracer::readLabel(JS::ExecutionTrace::EventKind kind, 310 JS::ExecutionTrace::TracedEvent& event, 311 TracingScratchBuffer& scratchBuffer, 312 mozilla::Vector<char>& stringBuffer) { 313 MOZ_ASSERT(kind == JS::ExecutionTrace::EventKind::LabelEnter || 314 kind == JS::ExecutionTrace::EventKind::LabelLeave); 315 316 event.kind = kind; 317 size_t index; 318 if (!inlineData_.readString(scratchBuffer, stringBuffer, &index)) { 319 return false; 320 } 321 event.labelEvent.label = index; 322 323 double time; 324 inlineData_.read(&time); 325 event.time = time; 326 327 return true; 328 } 329 330 bool ExecutionTracer::readInlineEntry( 331 mozilla::Vector<JS::ExecutionTrace::TracedEvent>& events, 332 TracingScratchBuffer& scratchBuffer, mozilla::Vector<char>& stringBuffer) { 333 uint8_t entryType; 334 inlineData_.read(&entryType); 335 336 switch (InlineEntryType(entryType)) { 337 case InlineEntryType::StackFunctionEnter: 338 case InlineEntryType::StackFunctionLeave: { 339 JS::ExecutionTrace::EventKind kind; 340 if (InlineEntryType(entryType) == InlineEntryType::StackFunctionEnter) { 341 kind = JS::ExecutionTrace::EventKind::FunctionEnter; 342 } else { 343 kind = JS::ExecutionTrace::EventKind::FunctionLeave; 344 } 345 JS::ExecutionTrace::TracedEvent event; 346 if (!readFunctionFrame(kind, event)) { 347 return false; 348 } 349 350 if (!events.append(std::move(event))) { 351 return false; 352 } 353 return true; 354 } 355 case InlineEntryType::LabelEnter: 356 case InlineEntryType::LabelLeave: { 357 JS::ExecutionTrace::EventKind kind; 358 if (InlineEntryType(entryType) == InlineEntryType::LabelEnter) { 359 kind = JS::ExecutionTrace::EventKind::LabelEnter; 360 } else { 361 kind = JS::ExecutionTrace::EventKind::LabelLeave; 362 } 363 364 JS::ExecutionTrace::TracedEvent event; 365 if (!readLabel(kind, event, scratchBuffer, stringBuffer)) { 366 return false; 367 } 368 369 if (!events.append(std::move(event))) { 370 return false; 371 } 372 373 return true; 374 } 375 case InlineEntryType::Error: { 376 JS::ExecutionTrace::TracedEvent event; 377 event.kind = JS::ExecutionTrace::EventKind::Error; 378 379 if (!events.append(std::move(event))) { 380 return false; 381 } 382 383 return true; 384 } 385 default: 386 return false; 387 } 388 } 389 390 bool ExecutionTracer::readOutOfLineEntry( 391 mozilla::HashMap<uint32_t, size_t>& scriptUrls, 392 mozilla::HashMap<uint32_t, size_t>& atoms, 393 mozilla::Vector<JS::ExecutionTrace::ShapeSummary>& shapes, 394 TracingScratchBuffer& scratchBuffer, mozilla::Vector<char>& stringBuffer) { 395 uint8_t entryType; 396 outOfLineData_.read(&entryType); 397 398 switch (OutOfLineEntryType(entryType)) { 399 case OutOfLineEntryType::ScriptURL: { 400 uint32_t id; 401 outOfLineData_.read(&id); 402 403 size_t index; 404 if (!outOfLineData_.readString(scratchBuffer, stringBuffer, &index)) { 405 return false; 406 } 407 408 if (!scriptUrls.put(id, index)) { 409 return false; 410 } 411 412 return true; 413 } 414 case OutOfLineEntryType::Atom: { 415 uint32_t id; 416 outOfLineData_.read(&id); 417 418 size_t index; 419 if (!outOfLineData_.readString(scratchBuffer, stringBuffer, &index)) { 420 return false; 421 } 422 423 if (!atoms.put(id, index)) { 424 return false; 425 } 426 427 return true; 428 } 429 case OutOfLineEntryType::Shape: { 430 JS::ExecutionTrace::ShapeSummary shape; 431 outOfLineData_.read(&shape.id); 432 outOfLineData_.read(&shape.numProperties); 433 shape.stringBufferOffset = stringBuffer.length(); 434 435 size_t dummyIndex; 436 if (!outOfLineData_.readString(scratchBuffer, stringBuffer, 437 &dummyIndex)) { 438 return false; 439 } 440 441 size_t realPropertyCount = 442 std::min(size_t(shape.numProperties), 443 size_t(JS::ValueSummary::MAX_COLLECTION_VALUES)); 444 for (uint32_t i = 0; i < realPropertyCount; ++i) { 445 uint8_t propKeyKind; 446 outOfLineData_.read(&propKeyKind); 447 switch (PropertyKeyKind(propKeyKind)) { 448 case PropertyKeyKind::Undefined: 449 if (!stringBuffer.growByUninitialized(sizeof("undefined"))) { 450 return false; 451 } 452 memcpy(stringBuffer.end() - sizeof("undefined"), "undefined", 453 sizeof("undefined")); 454 break; 455 case PropertyKeyKind::Symbol: { 456 constexpr size_t prefixLength = sizeof("Symbol(") - 1; 457 if (!stringBuffer.growByUninitialized(prefixLength)) { 458 return false; 459 } 460 memcpy(stringBuffer.end() - prefixLength, "Symbol(", prefixLength); 461 462 if (!outOfLineData_.readSmallString(scratchBuffer, stringBuffer, 463 &dummyIndex)) { 464 return false; 465 } 466 467 // Remove the null terminator 468 stringBuffer.shrinkBy(1); 469 if (!stringBuffer.append(')')) { 470 return false; 471 } 472 if (!stringBuffer.append(0)) { 473 return false; 474 } 475 476 break; 477 } 478 case PropertyKeyKind::Int: { 479 int32_t intVal; 480 outOfLineData_.read(&intVal); 481 size_t reserveLength = sizeof("-2147483648"); 482 if (!stringBuffer.reserve(stringBuffer.length() + reserveLength)) { 483 return false; 484 } 485 486 char* writePtr = stringBuffer.end(); 487 int len = SprintfBuf(writePtr, reserveLength, "%d", intVal); 488 489 if (!stringBuffer.growByUninitialized(len + 1)) { 490 return false; 491 } 492 break; 493 } 494 case PropertyKeyKind::String: { 495 if (!outOfLineData_.readSmallString(scratchBuffer, stringBuffer, 496 &dummyIndex)) { 497 return false; 498 } 499 break; 500 } 501 default: 502 MOZ_CRASH("Bad PropertyKeyKind"); 503 } 504 } 505 506 if (!shapes.append(shape)) { 507 return false; 508 } 509 510 return true; 511 } 512 default: 513 return false; 514 } 515 } 516 517 bool ExecutionTracer::readInlineEntries( 518 mozilla::Vector<JS::ExecutionTrace::TracedEvent>& events, 519 TracingScratchBuffer& scratchBuffer, mozilla::Vector<char>& stringBuffer) { 520 while (inlineData_.readable()) { 521 inlineData_.beginReadingEntry(); 522 if (!readInlineEntry(events, scratchBuffer, stringBuffer)) { 523 inlineData_.skipEntry(); 524 return false; 525 } 526 inlineData_.finishReadingEntry(); 527 } 528 return true; 529 } 530 531 bool ExecutionTracer::readOutOfLineEntries( 532 mozilla::HashMap<uint32_t, size_t>& scriptUrls, 533 mozilla::HashMap<uint32_t, size_t>& atoms, 534 mozilla::Vector<JS::ExecutionTrace::ShapeSummary>& shapes, 535 TracingScratchBuffer& scratchBuffer, mozilla::Vector<char>& stringBuffer) { 536 while (outOfLineData_.readable()) { 537 outOfLineData_.beginReadingEntry(); 538 if (!readOutOfLineEntry(scriptUrls, atoms, shapes, scratchBuffer, 539 stringBuffer)) { 540 outOfLineData_.skipEntry(); 541 return false; 542 } 543 outOfLineData_.finishReadingEntry(); 544 } 545 return true; 546 } 547 548 bool ExecutionTracer::getNativeTrace( 549 JS::ExecutionTrace::TracedJSContext& context, 550 TracingScratchBuffer& scratchBuffer, mozilla::Vector<char>& stringBuffer) { 551 LockGuard<Mutex> guard(bufferLock_); 552 553 if (!readOutOfLineEntries(context.scriptUrls, context.atoms, 554 context.shapeSummaries, scratchBuffer, 555 stringBuffer)) { 556 return false; 557 } 558 559 if (!readInlineEntries(context.events, scratchBuffer, stringBuffer)) { 560 return false; 561 } 562 563 if (!valueSummaries_.populateOutputBuffer(context)) { 564 return false; 565 } 566 567 return true; 568 } 569 570 bool ExecutionTracer::getNativeTraceForAllContexts(JS::ExecutionTrace& trace) { 571 LockGuard<Mutex> guard(globalInstanceLock); 572 TracingScratchBuffer scratchBuffer; 573 for (ExecutionTracer* tracer : globalInstances) { 574 JS::ExecutionTrace::TracedJSContext* context = nullptr; 575 for (JS::ExecutionTrace::TracedJSContext& t : trace.contexts) { 576 if (t.id == tracer->threadId_) { 577 context = &t; 578 break; 579 } 580 } 581 if (!context) { 582 if (!trace.contexts.append(JS::ExecutionTrace::TracedJSContext())) { 583 return false; 584 } 585 context = &trace.contexts[trace.contexts.length() - 1]; 586 context->id = tracer->threadId_; 587 } 588 if (!tracer->getNativeTrace(*context, scratchBuffer, trace.stringBuffer)) { 589 return false; 590 } 591 } 592 593 return true; 594 } 595 596 struct JS_TracerSummaryWriterImpl { 597 ValueSummaries* valueSummaries; 598 friend struct JS_TracerSummaryWriter; 599 }; 600 601 enum class GetNativeDataPropertyResult { 602 // We need to do something other than grab a value from a slot to read this. 603 // Either the class may want to resolve the id with a hook or we have to look 604 // it up on a proto that's not a NativeObject 605 Other, 606 607 // Simplest case: the property is just somewhere in the objects slots 608 DataProperty, 609 610 // The property is an accessor 611 Getter, 612 613 // The property is some kind of special derived property, like an Array's 614 // length, for example 615 CustomDataProperty, 616 617 // The property is missing from the object and its proto chain 618 Missing, 619 }; 620 621 // Note: `result` will only be set in the case where this returns 622 // GetNativeDataPropertyResult::DataProperty 623 GetNativeDataPropertyResult GetNativeDataProperty(JSContext* cx, 624 NativeObject* nobj, 625 JS::PropertyKey id, 626 JS::Value* result) { 627 while (true) { 628 MOZ_ASSERT(!nobj->getOpsLookupProperty()); 629 630 uint32_t index; 631 if (PropMap* map = nobj->shape()->lookup(cx, id, &index)) { 632 PropertyInfo prop = map->getPropertyInfo(index); 633 if (prop.isDataProperty()) { 634 *result = nobj->getSlot(prop.slot()); 635 return GetNativeDataPropertyResult::DataProperty; 636 } else if (prop.isCustomDataProperty()) { 637 return GetNativeDataPropertyResult::CustomDataProperty; 638 } 639 640 MOZ_ASSERT(prop.isAccessorProperty()); 641 return GetNativeDataPropertyResult::Getter; 642 } 643 644 if (!nobj->is<PlainObject>()) { 645 if (ClassMayResolveId(cx->names(), nobj->getClass(), id, nobj)) { 646 return GetNativeDataPropertyResult::Other; 647 } 648 } 649 650 JSObject* proto = nobj->staticPrototype(); 651 if (!proto) { 652 return GetNativeDataPropertyResult::Missing; 653 } 654 655 if (!proto->is<NativeObject>()) { 656 return GetNativeDataPropertyResult::Other; 657 } 658 nobj = &proto->as<NativeObject>(); 659 } 660 661 MOZ_ASSERT_UNREACHABLE(); 662 } 663 664 void ValueSummaries::writeHeader(JS::ValueType type, uint8_t flags) { 665 // 4 bits for the type, 4 bits for the flags 666 MOZ_ASSERT((uint8_t(type) & 0xF0) == 0); 667 MOZ_ASSERT((flags & 0xF0) == 0); 668 JS::ValueSummary header; 669 header.type = type; 670 header.flags = flags; 671 MOZ_ASSERT(*reinterpret_cast<uint8_t*>(&header) != 672 JS::ObjectSummary::GETTER_SETTER_MAGIC); 673 valueData_->writeBytes(reinterpret_cast<const uint8_t*>(&header), 674 sizeof(header)); 675 } 676 677 bool ValueSummaries::writeShapeSummary(JSContext* cx, 678 JS::Handle<NativeShape*> shape) { 679 TracingCaches& caches = cx->caches().tracingCaches; 680 681 uint32_t shapeId = 0; 682 TracingCaches::GetOrPutResult cacheResult = 683 caches.getOrPutShape(shape, &shapeId); 684 if (cacheResult == TracingCaches::GetOrPutResult::OOM) { 685 ReportOutOfMemory(cx); 686 return false; 687 } 688 if (cacheResult == TracingCaches::GetOrPutResult::NewlyAdded) { 689 outOfLineData_->beginWritingEntry(); 690 outOfLineData_->write(uint8_t(OutOfLineEntryType::Shape)); 691 outOfLineData_->write(shapeId); 692 693 uint32_t numProps = 0; 694 for (ShapePropertyIter<NoGC> iter(shape); !iter.done(); iter++) { 695 if (iter->isCustomDataProperty()) { 696 continue; 697 } 698 numProps += 1; 699 } 700 701 outOfLineData_->write(numProps); 702 outOfLineData_->writeCString<char, JS::TracerStringEncoding::Latin1>( 703 shape->getObjectClass()->name); 704 705 uint32_t countWritten = 0; 706 for (ShapePropertyIter<NoGC> iter(shape); !iter.done(); iter++) { 707 if (iter->isCustomDataProperty()) { 708 continue; 709 } 710 PropertyKey key = iter->key(); 711 if (key.isVoid()) { 712 outOfLineData_->write(uint8_t(PropertyKeyKind::Undefined)); 713 } else if (key.isInt()) { 714 outOfLineData_->write(uint8_t(PropertyKeyKind::Int)); 715 outOfLineData_->write(key.toInt()); 716 } else if (key.isSymbol()) { 717 outOfLineData_->write(uint8_t(PropertyKeyKind::Symbol)); 718 JS::Rooted<JSString*> str(cx, key.toSymbol()->description()); 719 if (str) { 720 if (!outOfLineData_->writeSmallString(cx, str)) { 721 return false; 722 } 723 } else { 724 outOfLineData_ 725 ->writeSmallCString<char, JS::TracerStringEncoding::Latin1>( 726 "<unknown>"); 727 } 728 } else if (key.isString()) { 729 outOfLineData_->write(uint8_t(PropertyKeyKind::String)); 730 JS::Rooted<JSString*> str(cx, key.toString()); 731 if (!outOfLineData_->writeSmallString(cx, str)) { 732 return false; 733 } 734 } 735 if (++countWritten >= JS::ValueSummary::MAX_COLLECTION_VALUES) { 736 break; 737 } 738 } 739 outOfLineData_->finishWritingEntry(); 740 } 741 742 valueData_->write(shapeId); 743 return true; 744 } 745 746 bool ValueSummaries::writeErrorObjectSummary(JSContext* cx, 747 JS::Handle<JSObject*> obj, 748 JS::Handle<ErrorObject*> error, 749 IsNested nested) { 750 writeObjectHeader(JS::ObjectSummary::Kind::Error, 0); 751 752 JS::Rooted<Shape*> shape(cx, obj->shape()); 753 if (!writeMinimalShapeSummary(cx, shape)) { 754 return false; 755 } 756 757 JS::Rooted<JS::Value> nameVal(cx); 758 JS::Rooted<JSString*> name(cx); 759 if (!GetProperty(cx, obj, obj, cx->names().name, &nameVal) || 760 !(name = ToString<CanGC>(cx, nameVal))) { 761 valueData_->writeEmptySmallString(); 762 } else { 763 if (!valueData_->writeSmallString(cx, name)) { 764 return false; 765 } 766 } 767 768 JS::Rooted<JSString*> message(cx, error->getMessage()); 769 if (message) { 770 if (!valueData_->writeSmallString(cx, message)) { 771 return false; 772 } 773 } else { 774 valueData_->writeEmptySmallString(); 775 } 776 777 JS::Rooted<JSObject*> stack(cx, error->stack()); 778 if (stack) { 779 JSPrincipals* principals = 780 JS::GetRealmPrincipals(js::GetNonCCWObjectRealm(stack)); 781 JS::Rooted<JSString*> formattedStack(cx); 782 if (!JS::BuildStackString(cx, principals, stack, &formattedStack)) { 783 return false; 784 } 785 if (!valueData_->writeSmallString(cx, formattedStack)) { 786 return false; 787 } 788 } else { 789 valueData_->writeEmptySmallString(); 790 } 791 792 JS::Rooted<JSString*> filename(cx, error->fileName(cx)); 793 if (filename) { 794 if (!valueData_->writeSmallString(cx, filename)) { 795 return false; 796 } 797 } else { 798 valueData_->writeEmptySmallString(); 799 } 800 801 valueData_->write((uint32_t)error->lineNumber()); 802 803 valueData_->write((uint32_t)error->columnNumber().oneOriginValue()); 804 805 return true; 806 } 807 808 bool ValueSummaries::writeMinimalShapeSummary(JSContext* cx, 809 JS::Handle<Shape*> shape) { 810 TracingCaches& caches = cx->caches().tracingCaches; 811 812 uint32_t shapeId = 0; 813 TracingCaches::GetOrPutResult cacheResult = 814 caches.getOrPutShape(shape, &shapeId); 815 if (cacheResult == TracingCaches::GetOrPutResult::OOM) { 816 ReportOutOfMemory(cx); 817 return false; 818 } 819 if (cacheResult == TracingCaches::GetOrPutResult::NewlyAdded) { 820 outOfLineData_->beginWritingEntry(); 821 outOfLineData_->write(uint8_t(OutOfLineEntryType::Shape)); 822 outOfLineData_->write(shapeId); 823 824 outOfLineData_->write(uint32_t(0)); // numProps 825 outOfLineData_->writeCString<char, JS::TracerStringEncoding::Latin1>( 826 shape->getObjectClass()->name); 827 828 outOfLineData_->finishWritingEntry(); 829 } 830 831 valueData_->write(shapeId); 832 return true; 833 } 834 835 void ValueSummaries::writeObjectHeader(JS::ObjectSummary::Kind kind, 836 uint8_t flags) { 837 writeHeader(JS::ValueType::Object, flags); 838 JS::ObjectSummary header; 839 header.kind = kind; 840 valueData_->writeBytes(reinterpret_cast<const uint8_t*>(&header), 841 sizeof(header)); 842 } 843 844 bool ValueSummaries::writeFunctionSummary(JSContext* cx, 845 JS::Handle<JSFunction*> fn, 846 IsNested nested) { 847 writeObjectHeader(JS::ObjectSummary::Kind::Function, 0); 848 849 JS::Rooted<JSAtom*> functionName(cx); 850 if (!GetFunctionName(cx, fn, &functionName)) { 851 return false; 852 } 853 854 if (functionName) { 855 if (!valueData_->writeSmallString(cx, functionName)) { 856 return false; 857 } 858 } else { 859 valueData_->writeEmptySmallString(); 860 } 861 862 JS::Rooted<ArrayObject*> parameterNames( 863 cx, GetFunctionParameterNamesArray(cx, fn)); 864 if (!parameterNames) { 865 return false; 866 } 867 868 uint32_t length = parameterNames->length(); 869 870 valueData_->write(length); 871 if (length > JS::ValueSummary::MAX_COLLECTION_VALUES) { 872 length = JS::ValueSummary::MAX_COLLECTION_VALUES; 873 } 874 MOZ_RELEASE_ASSERT(parameterNames->getDenseInitializedLength() >= length); 875 876 for (uint32_t i = 0; i < length; ++i) { 877 if (parameterNames->getDenseElement(i).isString()) { 878 JS::Rooted<JSString*> str(cx, 879 parameterNames->getDenseElement(i).toString()); 880 if (!valueData_->writeSmallString(cx, str)) { 881 return false; 882 } 883 } else { 884 valueData_->writeEmptySmallString(); 885 } 886 } 887 888 return true; 889 } 890 891 bool ValueSummaries::writeArrayObjectSummary(JSContext* cx, 892 JS::Handle<ArrayObject*> arr, 893 IsNested nested) { 894 writeObjectHeader(JS::ObjectSummary::Kind::ArrayLike, 0); 895 896 JS::Rooted<Shape*> shape(cx, arr->shape()); 897 if (!writeMinimalShapeSummary(cx, shape)) { 898 return false; 899 } 900 901 size_t length = arr->length(); 902 MOZ_ASSERT(length == uint32_t(length)); 903 valueData_->write(uint32_t(length)); 904 905 if (nested == IsNested::Yes) { 906 return true; 907 } 908 909 size_t initlen = arr->getDenseInitializedLength(); 910 for (uint32_t i = 0; 911 i < initlen && i < JS::ValueSummary::MAX_COLLECTION_VALUES; ++i) { 912 JS::Rooted<JS::Value> rv(cx, arr->getDenseElement(i)); 913 if (!writeValue(cx, rv, IsNested::Yes)) { 914 return false; 915 } 916 } 917 918 for (uint32_t i = initlen; 919 i < length && i < JS::ValueSummary::MAX_COLLECTION_VALUES; ++i) { 920 // Write holes into the array to fill out the discrepancy between the 921 // length and the dense initialized length. 922 writeHeader(JS::ValueType::Magic, 0); 923 } 924 925 return true; 926 } 927 928 bool ValueSummaries::writeSetObjectSummary(JSContext* cx, 929 JS::Handle<SetObject*> obj, 930 IsNested nested) { 931 writeObjectHeader(JS::ObjectSummary::Kind::ArrayLike, 0); 932 933 JS::Rooted<Shape*> shape(cx, obj->shape()); 934 if (!writeMinimalShapeSummary(cx, shape)) { 935 return false; 936 } 937 938 JS::Rooted<GCVector<JS::Value>> keys(cx, GCVector<JS::Value>(cx)); 939 if (!obj->keys(&keys)) { 940 return false; 941 } 942 943 valueData_->write(uint32_t(keys.length())); 944 945 if (nested == IsNested::Yes) { 946 return true; 947 } 948 949 for (size_t i = 0; 950 i < keys.length() && i < JS::ValueSummary::MAX_COLLECTION_VALUES; ++i) { 951 JS::Rooted<JS::Value> val(cx, keys[i]); 952 if (!writeValue(cx, val, IsNested::Yes)) { 953 return false; 954 } 955 } 956 957 return true; 958 } 959 960 bool ValueSummaries::writeMapObjectSummary(JSContext* cx, 961 JS::Handle<MapObject*> obj, 962 IsNested nested) { 963 writeObjectHeader(JS::ObjectSummary::Kind::MapLike, 0); 964 965 JS::Rooted<Shape*> shape(cx, obj->shape()); 966 if (!writeMinimalShapeSummary(cx, shape)) { 967 return false; 968 } 969 970 valueData_->write(obj->size()); 971 972 if (nested == IsNested::Yes) { 973 return true; 974 } 975 976 JS::Rooted<JS::Value> iter(cx); 977 if (!JS::MapEntries(cx, obj, &iter)) { 978 return false; 979 } 980 JS::Rooted<js::MapIteratorObject*> miter( 981 cx, &iter.toObject().as<js::MapIteratorObject>()); 982 JS::Rooted<ArrayObject*> entryPair( 983 cx, 984 static_cast<ArrayObject*>(js::MapIteratorObject::createResultPair(cx))); 985 if (!entryPair) { 986 return false; 987 } 988 989 uint32_t count = 0; 990 while (!js::MapIteratorObject::next(miter, entryPair)) { 991 JS::Rooted<JS::Value> key(cx, entryPair->getDenseElement(0)); 992 JS::Rooted<JS::Value> val(cx, entryPair->getDenseElement(1)); 993 if (!writeValue(cx, key, IsNested::Yes)) { 994 return false; 995 } 996 997 if (!writeValue(cx, val, IsNested::Yes)) { 998 return false; 999 } 1000 1001 if (++count >= JS::ValueSummary::MAX_COLLECTION_VALUES) { 1002 break; 1003 } 1004 } 1005 1006 return true; 1007 } 1008 1009 bool ValueSummaries::writeGenericOrWrappedPrimitiveObjectSummary( 1010 JSContext* cx, JS::Handle<NativeObject*> nobj, IsNested nested) { 1011 uint8_t flags = 0; 1012 if (nobj->getDenseInitializedLength() > 0) { 1013 flags |= JS::ValueSummary::GENERIC_OBJECT_HAS_DENSE_ELEMENTS; 1014 } 1015 1016 if (nobj->is<StringObject>()) { 1017 writeObjectHeader(JS::ObjectSummary::Kind::WrappedPrimitiveObject, flags); 1018 JS::Rooted<JS::Value> val(cx, 1019 StringValue(nobj->as<StringObject>().unbox())); 1020 if (!writeValue(cx, val, IsNested::Yes)) { 1021 return false; 1022 } 1023 } else if (nobj->is<BooleanObject>()) { 1024 writeObjectHeader(JS::ObjectSummary::Kind::WrappedPrimitiveObject, flags); 1025 JS::Rooted<JS::Value> val(cx, 1026 BooleanValue(nobj->as<BooleanObject>().unbox())); 1027 if (!writeValue(cx, val, IsNested::Yes)) { 1028 return false; 1029 } 1030 } else if (nobj->is<NumberObject>()) { 1031 writeObjectHeader(JS::ObjectSummary::Kind::WrappedPrimitiveObject, flags); 1032 JS::Rooted<JS::Value> val(cx, 1033 NumberValue(nobj->as<NumberObject>().unbox())); 1034 if (!writeValue(cx, val, IsNested::Yes)) { 1035 return false; 1036 } 1037 } else if (nobj->is<SymbolObject>()) { 1038 writeObjectHeader(JS::ObjectSummary::Kind::WrappedPrimitiveObject, flags); 1039 JS::Rooted<JS::Value> val(cx, 1040 SymbolValue(nobj->as<SymbolObject>().unbox())); 1041 if (!writeValue(cx, val, IsNested::Yes)) { 1042 return false; 1043 } 1044 } else if (nobj->is<BigIntObject>()) { 1045 writeObjectHeader(JS::ObjectSummary::Kind::WrappedPrimitiveObject, flags); 1046 JS::Rooted<JS::Value> val(cx, 1047 BigIntValue(nobj->as<BigIntObject>().unbox())); 1048 if (!writeValue(cx, val, IsNested::Yes)) { 1049 return false; 1050 } 1051 } else { 1052 writeObjectHeader(JS::ObjectSummary::Kind::GenericObject, flags); 1053 } 1054 1055 JS::Rooted<NativeShape*> shape(cx, nobj->shape()); 1056 if (!writeShapeSummary(cx, shape)) { 1057 return false; 1058 } 1059 1060 uint32_t numProps = 0; 1061 for (ShapePropertyIter<NoGC> iter(shape); !iter.done(); iter++) { 1062 if (iter->isCustomDataProperty()) { 1063 continue; 1064 } 1065 numProps += 1; 1066 } 1067 valueData_->write(numProps); 1068 1069 if (nested == IsNested::No) { 1070 size_t countWritten = 0; 1071 for (ShapePropertyIter<CanGC> iter(cx, nobj->shape()); !iter.done(); 1072 iter++) { 1073 if (iter->isCustomDataProperty()) { 1074 continue; 1075 } 1076 1077 if (iter->isDataProperty()) { 1078 JS::Rooted<JS::Value> rv(cx, nobj->getSlot(iter->slot())); 1079 if (!writeValue(cx, rv, IsNested::Yes)) { 1080 return false; 1081 } 1082 } else { 1083 valueData_->write(JS::ObjectSummary::GETTER_SETTER_MAGIC); 1084 MOZ_ASSERT(iter->isAccessorProperty()); 1085 JS::Rooted<JS::Value> getter(cx, nobj->getGetterValue(*iter)); 1086 if (!writeValue(cx, getter, IsNested::Yes)) { 1087 return false; 1088 } 1089 JS::Rooted<JS::Value> setter(cx, nobj->getSetterValue(*iter)); 1090 if (!writeValue(cx, setter, IsNested::Yes)) { 1091 return false; 1092 } 1093 } 1094 1095 if (++countWritten >= JS::ValueSummary::MAX_COLLECTION_VALUES) { 1096 break; 1097 } 1098 } 1099 } 1100 1101 // If this condition is true, GENERIC_OBJECT_HAS_DENSE_ELEMENTS will have 1102 // been set on the ValueSummary flags, allowing the reader to know to expect 1103 // an array of additional values here. 1104 if (nobj->getDenseInitializedLength() > 0) { 1105 size_t initlen = nobj->getDenseInitializedLength(); 1106 MOZ_ASSERT(initlen == uint32_t(initlen)); 1107 valueData_->write(uint32_t(initlen)); 1108 1109 if (nested == IsNested::No) { 1110 for (uint32_t i = 0; 1111 i < initlen && i < JS::ValueSummary::MAX_COLLECTION_VALUES; ++i) { 1112 JS::Rooted<JS::Value> rv(cx, nobj->getDenseElement(i)); 1113 if (!writeValue(cx, rv, IsNested::Yes)) { 1114 return false; 1115 } 1116 } 1117 } 1118 } 1119 1120 return true; 1121 } 1122 1123 bool ValueSummaries::writeExternalObjectSummary(JSContext* cx, 1124 JS::Handle<NativeObject*> obj, 1125 IsNested nested) { 1126 writeObjectHeader(JS::ObjectSummary::Kind::External, 0); 1127 1128 JS::Rooted<Shape*> shape(cx, obj->shape()); 1129 if (!writeMinimalShapeSummary(cx, shape)) { 1130 return false; 1131 } 1132 1133 // Save space for the external size written, which we'll populate after 1134 // calling the callback. 1135 uint64_t externalSizeOffset = valueData_->uncommittedWriteHead(); 1136 valueData_->write(uint32_t(0)); 1137 1138 JS_TracerSummaryWriterImpl writerImpl = {this}; 1139 JS_TracerSummaryWriter writer = {&writerImpl}; 1140 CustomObjectSummaryCallback cb = cx->getCustomObjectSummaryCallback(); 1141 MOZ_ASSERT(cb); 1142 if (!cb(cx, obj, nested == IsNested::Yes, &writer)) { 1143 return false; 1144 } 1145 1146 uint64_t amountWritten64 = 1147 valueData_->uncommittedWriteHead() - externalSizeOffset; 1148 MOZ_ASSERT(amountWritten64 + sizeof(uint32_t) < ValueDataBuffer::SIZE); 1149 uint32_t amountWritten = uint32_t(amountWritten64); 1150 1151 valueData_->writeAtOffset(amountWritten, externalSizeOffset); 1152 1153 return true; 1154 } 1155 1156 bool ValueSummaries::writeObject(JSContext* cx, JS::Handle<JSObject*> obj, 1157 IsNested nested) { 1158 if (obj->is<JSFunction>()) { 1159 JS::Rooted<JSFunction*> typed(cx, &obj->as<JSFunction>()); 1160 if (!writeFunctionSummary(cx, typed, nested)) { 1161 return false; 1162 } 1163 } else if (obj->is<ArrayObject>()) { 1164 JS::Rooted<ArrayObject*> typed(cx, &obj->as<ArrayObject>()); 1165 if (!writeArrayObjectSummary(cx, typed, nested)) { 1166 return false; 1167 } 1168 } else if (obj->is<SetObject>()) { 1169 JS::Rooted<SetObject*> typed(cx, &obj->as<SetObject>()); 1170 if (!writeSetObjectSummary(cx, typed, nested)) { 1171 return false; 1172 } 1173 } else if (obj->is<MapObject>()) { 1174 JS::Rooted<MapObject*> typed(cx, &obj->as<MapObject>()); 1175 if (!writeMapObjectSummary(cx, typed, nested)) { 1176 return false; 1177 } 1178 } else if (obj->is<ErrorObject>()) { 1179 JS::Rooted<ErrorObject*> typed(cx, &obj->as<ErrorObject>()); 1180 if (!writeErrorObjectSummary(cx, obj, typed, nested)) { 1181 return false; 1182 } 1183 } else if (obj->is<NativeObject>()) { 1184 JS::Rooted<NativeObject*> nobj(cx, &obj->as<NativeObject>()); 1185 1186 // TODO: see the comment in Debug.h for Kind::External 1187 if (cx->getCustomObjectSummaryCallback() && 1188 nobj->shape()->getObjectClass()->flags & JSCLASS_IS_DOMJSCLASS) { 1189 if (!writeExternalObjectSummary(cx, nobj, nested)) { 1190 return false; 1191 } 1192 } else { 1193 if (!writeGenericOrWrappedPrimitiveObjectSummary(cx, nobj, nested)) { 1194 return false; 1195 } 1196 } 1197 } else if (obj->is<ProxyObject>()) { 1198 writeObjectHeader(JS::ObjectSummary::Kind::ProxyObject, 0); 1199 JS::Rooted<Shape*> shape(cx, obj->shape()); 1200 if (!writeMinimalShapeSummary(cx, shape)) { 1201 return false; 1202 } 1203 } else { 1204 writeObjectHeader(JS::ObjectSummary::Kind::NotImplemented, 0); 1205 JS::Rooted<Shape*> shape(cx, obj->shape()); 1206 if (!writeMinimalShapeSummary(cx, shape)) { 1207 return false; 1208 } 1209 } 1210 1211 return true; 1212 } 1213 1214 bool ValueSummaries::writeArguments(JSContext* cx, AbstractFramePtr frame, 1215 uint64_t* valueBufferIndex) { 1216 uint32_t argc = frame.numActualArgs(); 1217 1218 valueData_->beginWritingEntry(); 1219 *valueBufferIndex = valueData_->uncommittedWriteHead(); 1220 1221 if (argc > JS::ExecutionTrace::MAX_ARGUMENTS_TO_RECORD) { 1222 argc = JS::ExecutionTrace::MAX_ARGUMENTS_TO_RECORD; 1223 } 1224 valueData_->write(argc); 1225 1226 for (uint32_t i = 0; 1227 i < argc && i < JS::ExecutionTrace::MAX_ARGUMENTS_TO_RECORD; ++i) { 1228 Rooted<JS::Value> val(cx, frame.argv()[i]); 1229 if (!writeValue(cx, val, IsNested::No)) { 1230 return false; 1231 } 1232 } 1233 valueData_->finishWritingEntry(); 1234 1235 return true; 1236 } 1237 1238 bool ValueSummaries::populateOutputBuffer( 1239 JS::ExecutionTrace::TracedJSContext& context) { 1240 size_t valueBytes = 1241 valueData_->uncommittedWriteHead() - valueData_->readHead(); 1242 if (!context.valueBuffer.initLengthUninitialized( 1243 valueBytes + sizeof(JS::ValueSummary::VERSION))) { 1244 return false; 1245 } 1246 uint32_t version = 1247 mozilla::NativeEndian::swapToLittleEndian(JS::ValueSummary::VERSION); 1248 memcpy(context.valueBuffer.begin(), &version, sizeof(version)); 1249 1250 valueData_->readBytes( 1251 context.valueBuffer.begin() + sizeof(JS::ValueSummary::VERSION), 1252 valueBytes); 1253 return true; 1254 } 1255 1256 int32_t ValueSummaries::getOutputBufferIndex(uint64_t argumentsIndex) { 1257 if (argumentsIndex > valueData_->readHead()) { 1258 MOZ_ASSERT(argumentsIndex - valueData_->readHead() < 1259 std::numeric_limits<int32_t>::max() - sizeof(uint32_t) - 1260 sizeof(JS::ValueSummary::VERSION)); 1261 return int32_t(argumentsIndex - valueData_->readHead() + 1262 sizeof(JS::ValueSummary::VERSION)); 1263 } 1264 1265 return JS::ExecutionTrace::EXPIRED_VALUES_MAGIC; 1266 } 1267 1268 bool ValueSummaries::writeStringLikeValue(JSContext* cx, 1269 JS::ValueType valueType, 1270 JS::Handle<JSString*> str) { 1271 writeHeader(valueType, 0); 1272 return valueData_->writeSmallString(cx, str); 1273 } 1274 1275 bool ValueSummaries::writeValue(JSContext* cx, JS::Handle<JS::Value> val, 1276 IsNested nested) { 1277 switch (val.type()) { 1278 case JS::ValueType::Double: 1279 if (mozilla::IsPositiveZero(val.toDouble())) { 1280 writeHeader(JS::ValueType::Double, 0); 1281 } else { 1282 writeHeader(JS::ValueType::Double, 1283 JS::ValueSummary::NUMBER_IS_OUT_OF_LINE_MAGIC); 1284 valueData_->write(val.toDouble()); 1285 } 1286 return true; 1287 case JS::ValueType::Int32: { 1288 int32_t intVal = val.toInt32(); 1289 if (intVal > JS::ValueSummary::MAX_INLINE_INT || 1290 intVal < JS::ValueSummary::MIN_INLINE_INT) { 1291 writeHeader(JS::ValueType::Int32, 1292 JS::ValueSummary::NUMBER_IS_OUT_OF_LINE_MAGIC); 1293 valueData_->write(val.toInt32()); 1294 } else { 1295 writeHeader(JS::ValueType::Int32, 1296 intVal - JS::ValueSummary::MIN_INLINE_INT); 1297 } 1298 return true; 1299 } 1300 case JS::ValueType::Boolean: 1301 writeHeader(JS::ValueType::Boolean, uint8_t(val.toBoolean())); 1302 return true; 1303 case JS::ValueType::Magic: 1304 // The one kind of magic we can actually see is a hole in the dense 1305 // elements of an object, which will need to be specially interpreted 1306 // as such by the reader. 1307 MOZ_ASSERT(val.isMagic(JSWhyMagic::JS_ELEMENTS_HOLE)); 1308 writeHeader(JS::ValueType::Magic, 0); 1309 return true; 1310 case JS::ValueType::Undefined: 1311 writeHeader(JS::ValueType::Undefined, 0); 1312 return true; 1313 case JS::ValueType::Null: 1314 writeHeader(JS::ValueType::Null, 0); 1315 return true; 1316 case JS::ValueType::BigInt: { 1317 JS::Rooted<JS::BigInt*> bi(cx, val.toBigInt()); 1318 JS::Rooted<JSString*> str(cx, BigInt::toString<CanGC>(cx, bi, 10)); 1319 if (!str) { 1320 return false; 1321 } 1322 return writeStringLikeValue(cx, JS::ValueType::BigInt, str); 1323 } 1324 case JS::ValueType::Symbol: { 1325 JS::Rooted<JSString*> str(cx, val.toSymbol()->description()); 1326 if (!str) { 1327 writeHeader(JS::ValueType::Symbol, 1328 JS::ValueSummary::SYMBOL_NO_DESCRIPTION); 1329 return true; 1330 } 1331 return writeStringLikeValue(cx, JS::ValueType::Symbol, str); 1332 } 1333 case JS::ValueType::String: { 1334 JS::Rooted<JSString*> str(cx, val.toString()); 1335 return writeStringLikeValue(cx, JS::ValueType::String, str); 1336 } 1337 case JS::ValueType::Object: { 1338 JS::Rooted<JSObject*> obj(cx, &val.toObject()); 1339 mozilla::Maybe<AutoRealm> ar; 1340 if (IsCrossCompartmentWrapper(obj)) { 1341 obj = UncheckedUnwrap(obj, true); 1342 ar.emplace(cx, obj); 1343 } 1344 return writeObject(cx, obj, nested); 1345 } 1346 default: 1347 MOZ_CRASH("Unexpected value type in JS Execution Tracer"); 1348 return false; 1349 } 1350 } 1351 1352 void JS_TracerSummaryWriter::writeUint8(uint8_t val) { 1353 impl->valueSummaries->valueData_->write(val); 1354 } 1355 1356 void JS_TracerSummaryWriter::writeUint16(uint16_t val) { 1357 impl->valueSummaries->valueData_->write(val); 1358 } 1359 1360 void JS_TracerSummaryWriter::writeUint32(uint32_t val) { 1361 impl->valueSummaries->valueData_->write(val); 1362 } 1363 1364 void JS_TracerSummaryWriter::writeUint64(uint64_t val) { 1365 impl->valueSummaries->valueData_->write(val); 1366 } 1367 1368 void JS_TracerSummaryWriter::writeInt8(int8_t val) { 1369 impl->valueSummaries->valueData_->write(val); 1370 } 1371 1372 void JS_TracerSummaryWriter::writeInt16(int16_t val) { 1373 impl->valueSummaries->valueData_->write(val); 1374 } 1375 1376 void JS_TracerSummaryWriter::writeInt32(int32_t val) { 1377 impl->valueSummaries->valueData_->write(val); 1378 } 1379 1380 void JS_TracerSummaryWriter::writeInt64(int64_t val) { 1381 impl->valueSummaries->valueData_->write(val); 1382 } 1383 1384 void JS_TracerSummaryWriter::writeUTF8String(const char* val) { 1385 impl->valueSummaries->valueData_ 1386 ->writeSmallCString<char, JS::TracerStringEncoding::UTF8>(val); 1387 } 1388 1389 void JS_TracerSummaryWriter::writeTwoByteString(const char16_t* val) { 1390 impl->valueSummaries->valueData_ 1391 ->writeSmallCString<char16_t, JS::TracerStringEncoding::TwoByte>(val); 1392 } 1393 1394 bool JS_TracerSummaryWriter::writeValue(JSContext* cx, 1395 JS::Handle<JS::Value> val) { 1396 return impl->valueSummaries->writeValue(cx, val, 1397 js::ValueSummaries::IsNested::Yes); 1398 } 1399 1400 void JS_SetCustomObjectSummaryCallback(JSContext* cx, 1401 CustomObjectSummaryCallback callback) { 1402 cx->setCustomObjectSummaryCallback(callback); 1403 } 1404 1405 void JS_TracerEnterLabelTwoByte(JSContext* cx, const char16_t* label) { 1406 CHECK_THREAD(cx); 1407 if (cx->hasExecutionTracer()) { 1408 cx->getExecutionTracer() 1409 .onEnterLabel<char16_t, JS::TracerStringEncoding::TwoByte>(label); 1410 } 1411 } 1412 1413 void JS_TracerEnterLabelLatin1(JSContext* cx, const char* label) { 1414 CHECK_THREAD(cx); 1415 if (cx->hasExecutionTracer()) { 1416 cx->getExecutionTracer() 1417 .onEnterLabel<char, JS::TracerStringEncoding::Latin1>(label); 1418 } 1419 } 1420 1421 void JS_TracerLeaveLabelTwoByte(JSContext* cx, const char16_t* label) { 1422 CHECK_THREAD(cx); 1423 if (cx->hasExecutionTracer()) { 1424 cx->getExecutionTracer() 1425 .onLeaveLabel<char16_t, JS::TracerStringEncoding::TwoByte>(label); 1426 } 1427 } 1428 1429 void JS_TracerLeaveLabelLatin1(JSContext* cx, const char* label) { 1430 CHECK_THREAD(cx); 1431 if (cx->hasExecutionTracer()) { 1432 cx->getExecutionTracer() 1433 .onLeaveLabel<char, JS::TracerStringEncoding::Latin1>(label); 1434 } 1435 } 1436 1437 bool JS_TracerIsTracing(JSContext* cx) { return cx->hasExecutionTracer(); } 1438 1439 bool JS_TracerBeginTracing(JSContext* cx) { 1440 CHECK_THREAD(cx); 1441 return cx->enableExecutionTracing(); 1442 } 1443 1444 bool JS_TracerEndTracing(JSContext* cx) { 1445 CHECK_THREAD(cx); 1446 cx->disableExecutionTracing(); 1447 return true; 1448 } 1449 1450 bool JS_TracerSnapshotTrace(JS::ExecutionTrace& trace) { 1451 return ExecutionTracer::getNativeTraceForAllContexts(trace); 1452 }