tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }