tor-browser

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

ExecutionTracer.h (25569B)


      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 #ifndef debugger_ExecutionTracer_h
      8 #define debugger_ExecutionTracer_h
      9 
     10 #include "mozilla/Assertions.h"         // MOZ_DIAGNOSTIC_ASSERT, MOZ_ASSERT
     11 #include "mozilla/BaseProfilerUtils.h"  // profiler_current_thread_id
     12 #include "mozilla/EndianUtils.h"        // NativeEndian
     13 #include "mozilla/MathAlgorithms.h"     // mozilla::IsPowerOfTwo
     14 #include "mozilla/Maybe.h"  // mozilla::Maybe, mozilla::Some, mozilla::Nothing
     15 #include "mozilla/Span.h"
     16 #include "mozilla/TimeStamp.h"
     17 
     18 #include <limits>    // std::numeric_limits
     19 #include <stddef.h>  // size_t
     20 #include <stdint.h>  // uint8_t, uint16_t
     21 
     22 #include "js/CharacterEncoding.h"  // JS::UTF8Chars
     23 #include "js/Debug.h"              // JS::ExecutionTrace
     24 #include "js/RootingAPI.h"         // JS::Rooted
     25 #include "js/Utility.h"            // js_malloc, js_free
     26 #include "js/Value.h"              // JS::Value
     27 #include "vm/ErrorObject.h"        // ErrorObject
     28 #include "vm/JSContext.h"          // JSContext
     29 #include "vm/Stack.h"              // js::AbstractFramePtr
     30 
     31 namespace js {
     32 
     33 enum class OutOfLineEntryType : uint8_t {
     34  ScriptURL,
     35  Atom,
     36  Shape,
     37 };
     38 
     39 enum class InlineEntryType : uint8_t {
     40  StackFunctionEnter,
     41  StackFunctionLeave,
     42  LabelEnter,
     43  LabelLeave,
     44  Error,
     45 };
     46 
     47 enum class PropertyKeyKind : uint8_t {
     48  Undefined,
     49  String,
     50  Int,
     51  Symbol,
     52 };
     53 
     54 using TracingScratchBuffer = mozilla::Vector<char, 512>;
     55 
     56 // TODO: it should be noted that part of this design is informed by the fact
     57 // that it evolved from a prototype which wrote this data from a content
     58 // process and read it from the parent process, allowing the parent process to
     59 // display the trace in real time as the program executes. Bug 1910182 tracks
     60 // the next steps for making that prototype a reality.
     61 template <size_t BUFFER_SIZE>
     62 class TracingBuffer {
     63  static_assert(mozilla::IsPowerOfTwo(BUFFER_SIZE));
     64 
     65  // BUFFER_SIZE is the size of the underlying ring buffer, and BUFFER_MASK
     66  // masks off indices into it in order to wrap around
     67  static const size_t BUFFER_MASK = BUFFER_SIZE - 1;
     68 
     69  // The entry header is just a u16 that holds the size of the entry in bytes.
     70  // This is used for asserting the integrity of the data as well as for
     71  // skipping the read head forward if it's going to be overwritten by the
     72  // write head
     73  static const size_t ENTRY_HEADER_SIZE = sizeof(uint16_t);
     74 
     75  // The underlying ring buffer
     76  uint8_t* buffer_ = nullptr;
     77 
     78  // NOTE: The following u64s are unwrapped indices into the ring buffer, so
     79  // they must always be masked off with BUFFER_MASK before using them to
     80  // access buffer_:
     81 
     82  // Represents how much has been written into the ring buffer and is ready
     83  // for reading
     84  uint64_t writeHead_ = 0;
     85 
     86  // Represents how much has been read from the ring buffer
     87  uint64_t readHead_ = 0;
     88 
     89  // When not equal to writeHead_, this represents unfinished write progress
     90  // into the buffer. After each entry successfully finished writing,
     91  // writeHead_ is set to this value
     92  uint64_t uncommittedWriteHead_ = 0;
     93 
     94  // Similar to uncommittedWriteHead_, but for the purposes of reading
     95  uint64_t uncommittedReadHead_ = 0;
     96 
     97  bool ensureScratchBufferSize(TracingScratchBuffer& scratchBuffer,
     98                               size_t requiredSize) {
     99    if (scratchBuffer.length() >= requiredSize) {
    100      return true;
    101    }
    102    return scratchBuffer.growByUninitialized(requiredSize -
    103                                             scratchBuffer.length());
    104  }
    105 
    106 public:
    107  static const size_t SIZE = BUFFER_SIZE;
    108 
    109  ~TracingBuffer() {
    110    if (buffer_) {
    111      js_free(buffer_);
    112    }
    113  }
    114 
    115  bool init() {
    116    buffer_ = static_cast<uint8_t*>(js_malloc(BUFFER_SIZE));
    117    return buffer_ != nullptr;
    118  }
    119 
    120  bool readable() { return writeHead_ > readHead_; }
    121 
    122  uint64_t uncommittedWriteHead() { return uncommittedWriteHead_; }
    123 
    124  uint64_t readHead() { return readHead_; }
    125 
    126  void beginWritingEntry() {
    127    // uncommittedWriteHead_ can be > writeHead_ if a previous write failed.
    128    // In that case, this effectively discards whatever was written during that
    129    // time
    130    MOZ_ASSERT(uncommittedWriteHead_ >= writeHead_);
    131    uncommittedWriteHead_ = writeHead_;
    132    uncommittedWriteHead_ += ENTRY_HEADER_SIZE;
    133  }
    134 
    135  void finishWritingEntry() {
    136    MOZ_RELEASE_ASSERT(uncommittedWriteHead_ - writeHead_ <=
    137                       std::numeric_limits<uint16_t>::max());
    138    uint16_t entryHeader = uint16_t(uncommittedWriteHead_ - writeHead_);
    139    writeBytesAtOffset(reinterpret_cast<const uint8_t*>(&entryHeader),
    140                       sizeof(entryHeader), writeHead_);
    141    writeHead_ = uncommittedWriteHead_;
    142  }
    143 
    144  void beginReadingEntry() {
    145    MOZ_ASSERT(uncommittedReadHead_ == readHead_);
    146    // We will read the entry header (still pointed to by readHead_) from
    147    // inside finishReadingEntry
    148    uncommittedReadHead_ += ENTRY_HEADER_SIZE;
    149  }
    150 
    151  void finishReadingEntry() {
    152    uint16_t entryHeader;
    153    readBytesAtOffset(reinterpret_cast<uint8_t*>(&entryHeader),
    154                      sizeof(entryHeader), readHead_);
    155    size_t read = uncommittedReadHead_ - readHead_;
    156 
    157    MOZ_RELEASE_ASSERT(entryHeader == uint16_t(read));
    158    readHead_ += entryHeader;
    159    uncommittedReadHead_ = readHead_;
    160  }
    161 
    162  void skipEntry() {
    163    uint16_t entryHeader;
    164    readBytesAtOffset(reinterpret_cast<uint8_t*>(&entryHeader),
    165                      sizeof(entryHeader), readHead_);
    166    readHead_ += entryHeader;
    167    uncommittedReadHead_ = readHead_;
    168  }
    169 
    170  void writeBytesAtOffset(const uint8_t* bytes, size_t length,
    171                          uint64_t offset) {
    172    MOZ_ASSERT(offset + length <= readHead_ + BUFFER_SIZE);
    173 
    174    size_t maskedWriteHead = offset & BUFFER_MASK;
    175    if (maskedWriteHead + length > BUFFER_SIZE) {
    176      size_t firstChunk = BUFFER_SIZE - maskedWriteHead;
    177      memcpy(buffer_ + maskedWriteHead, bytes, firstChunk);
    178      memcpy(buffer_, bytes + firstChunk, length - firstChunk);
    179    } else {
    180      memcpy(buffer_ + maskedWriteHead, bytes, length);
    181    }
    182  }
    183 
    184  void writeBytes(const uint8_t* bytes, size_t length) {
    185    // Skip the read head forward if we're about to overwrite unread entries
    186    while (MOZ_UNLIKELY(uncommittedWriteHead_ + length >
    187                        readHead_ + BUFFER_SIZE)) {
    188      skipEntry();
    189    }
    190 
    191    writeBytesAtOffset(bytes, length, uncommittedWriteHead_);
    192    uncommittedWriteHead_ += length;
    193  }
    194 
    195  template <typename T>
    196  void write(T val) {
    197    // No magic hidden work allowed here - we are just reducing duplicate code
    198    // serializing integers and floats.
    199    static_assert(std::is_arithmetic_v<T>);
    200    if constexpr (sizeof(T) > 1) {
    201      val = mozilla::NativeEndian::swapToLittleEndian(val);
    202    }
    203 
    204    writeBytes(reinterpret_cast<const uint8_t*>(&val), sizeof(T));
    205  }
    206 
    207  template <typename T>
    208  void writeAtOffset(T val, uint64_t offset) {
    209    static_assert(std::is_arithmetic_v<T>);
    210    if constexpr (sizeof(T) > 1) {
    211      val = mozilla::NativeEndian::swapToLittleEndian(val);
    212    }
    213 
    214    writeBytesAtOffset(reinterpret_cast<const uint8_t*>(&val), sizeof(T),
    215                       offset);
    216  }
    217 
    218  void writeEmptyString() {
    219    write(uint8_t(JS::TracerStringEncoding::Latin1));
    220    write(uint32_t(0));  // length
    221  }
    222 
    223  void writeEmptySmallString() { write(uint16_t(0)); }
    224 
    225  enum class InlineStringEncoding { No, Yes };
    226 
    227  // Helper for writing the length and encoding, which are sometimes squished
    228  // into one value
    229  template <typename LengthType = uint32_t,
    230            InlineStringEncoding InlineEncoding = InlineStringEncoding::No>
    231  void writeAdjustedLengthAndEncoding(
    232      size_t* length, JS::TracerStringEncoding encoding,
    233      size_t lengthLimit = std::numeric_limits<LengthType>::max()) {
    234    if (*length > lengthLimit) {
    235      *length = lengthLimit;
    236    }
    237 
    238    if constexpr (InlineEncoding == InlineStringEncoding::No) {
    239      write(uint8_t(encoding));
    240      write(LengthType(*length));
    241    } else {
    242      constexpr LengthType encodingBits = 2;
    243      LengthType typedLength =
    244          LengthType(*length) |
    245          (uint16_t(encoding) << (sizeof(LengthType) * 8 - encodingBits));
    246      write(typedLength);
    247    }
    248  }
    249 
    250  template <typename LengthType = uint32_t,
    251            InlineStringEncoding InlineEncoding = InlineStringEncoding::No>
    252  bool writeString(
    253      JSContext* cx, JS::Handle<JSString*> str,
    254      size_t lengthLimit = std::numeric_limits<LengthType>::max()) {
    255    JS::TracerStringEncoding encoding;
    256    if (str->hasLatin1Chars()) {
    257      encoding = JS::TracerStringEncoding::Latin1;
    258    } else {
    259      encoding = JS::TracerStringEncoding::TwoByte;
    260    }
    261 
    262    // TODO: if ropes are common we can certainly serialize them without
    263    // linearizing - this is just easy
    264    JSLinearString* linear = str->ensureLinear(cx);
    265    if (!linear) {
    266      return false;
    267    }
    268 
    269    size_t length = linear->length();
    270    writeAdjustedLengthAndEncoding<LengthType, InlineEncoding>(
    271        &length, encoding, lengthLimit);
    272 
    273    size_t size = length;
    274    JS::AutoAssertNoGC nogc;
    275    const uint8_t* charBuffer = nullptr;
    276    if (encoding == JS::TracerStringEncoding::TwoByte) {
    277      size *= sizeof(char16_t);
    278      charBuffer = reinterpret_cast<const uint8_t*>(linear->twoByteChars(nogc));
    279    } else {
    280      charBuffer = reinterpret_cast<const uint8_t*>(linear->latin1Chars(nogc));
    281    }
    282    writeBytes(charBuffer, size);
    283    return true;
    284  }
    285 
    286  template <typename CharType, JS::TracerStringEncoding Encoding,
    287            typename LengthType = uint32_t,
    288            InlineStringEncoding InlineEncoding = InlineStringEncoding::No>
    289  void writeCString(
    290      const CharType* chars,
    291      size_t lengthLimit = std::numeric_limits<LengthType>::max()) {
    292    size_t length = std::char_traits<CharType>::length(chars);
    293    static_assert(sizeof(CharType) == 1 ||
    294                  Encoding == JS::TracerStringEncoding::TwoByte);
    295    static_assert(sizeof(CharType) <= 2);
    296 
    297    writeAdjustedLengthAndEncoding<LengthType, InlineEncoding>(
    298        &length, Encoding, lengthLimit);
    299 
    300    const size_t size = length * sizeof(CharType);
    301    writeBytes(reinterpret_cast<const uint8_t*>(chars), size);
    302  }
    303 
    304  bool writeSmallString(JSContext* cx, JS::Handle<JSString*> str) {
    305    return writeString<uint16_t, InlineStringEncoding::Yes>(
    306        cx, str, JS::ValueSummary::SMALL_STRING_LENGTH_LIMIT);
    307  }
    308 
    309  template <typename CharType, JS::TracerStringEncoding Encoding>
    310  void writeSmallCString(const CharType* chars) {
    311    writeCString<CharType, Encoding, char16_t, InlineStringEncoding::Yes>(
    312        chars, JS::ValueSummary::SMALL_STRING_LENGTH_LIMIT);
    313  }
    314 
    315  void readBytesAtOffset(uint8_t* bytes, size_t length, uint64_t offset) {
    316    size_t maskedReadHead = offset & BUFFER_MASK;
    317    if (maskedReadHead + length > BUFFER_SIZE) {
    318      size_t firstChunk = BUFFER_SIZE - maskedReadHead;
    319      memcpy(bytes, buffer_ + maskedReadHead, firstChunk);
    320      memcpy(bytes + firstChunk, buffer_, length - firstChunk);
    321    } else {
    322      memcpy(bytes, buffer_ + maskedReadHead, length);
    323    }
    324  }
    325 
    326  void readBytes(uint8_t* bytes, size_t length) {
    327    readBytesAtOffset(bytes, length, uncommittedReadHead_);
    328    uncommittedReadHead_ += length;
    329  }
    330 
    331  template <typename T>
    332  void read(T* val) {
    333    static_assert(std::is_arithmetic_v<T>);
    334 
    335    readBytes(reinterpret_cast<uint8_t*>(val), sizeof(T));
    336    if constexpr (sizeof(T) > 1) {
    337      *val = mozilla::NativeEndian::swapFromLittleEndian(*val);
    338    }
    339  }
    340 
    341  // Reads a string from our buffer into the stringBuffer. Converts everything
    342  // to null-terminated UTF-8
    343  template <typename LengthType = uint32_t,
    344            InlineStringEncoding InlineEncoding = InlineStringEncoding::No>
    345  bool readString(TracingScratchBuffer& scratchBuffer,
    346                  mozilla::Vector<char>& stringBuffer, size_t* index) {
    347    uint8_t encodingByte;
    348    LengthType length;
    349    if constexpr (InlineEncoding == InlineStringEncoding::Yes) {
    350      LengthType lengthAndEncoding;
    351      read(&lengthAndEncoding);
    352      constexpr LengthType encodingBits = 2;
    353      constexpr LengthType encodingShift =
    354          sizeof(LengthType) * 8 - encodingBits;
    355      constexpr LengthType encodingMask = 0b11 << encodingShift;
    356      length = lengthAndEncoding & ~encodingMask;
    357      encodingByte = (lengthAndEncoding & encodingMask) >> encodingShift;
    358    } else {
    359      read(&encodingByte);
    360      read(&length);
    361    }
    362 
    363    JS::TracerStringEncoding encoding = JS::TracerStringEncoding(encodingByte);
    364 
    365    *index = stringBuffer.length();
    366 
    367    if (length == 0) {
    368      if (!stringBuffer.append('\0')) {
    369        return false;
    370      }
    371      return true;
    372    }
    373 
    374    if (encoding == JS::TracerStringEncoding::UTF8) {
    375      size_t reserveLength = length + 1;
    376      if (!stringBuffer.growByUninitialized(reserveLength)) {
    377        return false;
    378      }
    379      char* writePtr = stringBuffer.end() - reserveLength;
    380      readBytes(reinterpret_cast<uint8_t*>(writePtr), length);
    381      writePtr[length] = '\0';
    382    } else if (encoding == JS::TracerStringEncoding::Latin1) {
    383      if (!ensureScratchBufferSize(scratchBuffer, length)) {
    384        return false;
    385      }
    386      readBytes(reinterpret_cast<uint8_t*>(scratchBuffer.begin()), length);
    387 
    388      // A single latin-1 code point maps to either 1 or 2 UTF-8 code units.
    389      // The + 1 is for the null terminator.
    390      size_t reserveLength = length * 2 + 1;
    391      if (!stringBuffer.reserve(stringBuffer.length() + reserveLength)) {
    392        return false;
    393      }
    394      char* writePtr = stringBuffer.end();
    395 
    396      size_t convertedLength = mozilla::ConvertLatin1toUtf8(
    397          mozilla::Span<const char>(scratchBuffer.begin(), length),
    398          mozilla::Span<char>(writePtr, reserveLength));
    399      writePtr[convertedLength] = 0;
    400 
    401      // We reserved above, which just grows the capacity but not the length.
    402      // This just commits the exact length increase.
    403      if (!stringBuffer.growByUninitialized(convertedLength + 1)) {
    404        return false;
    405      }
    406    } else {
    407      MOZ_ASSERT(encoding == JS::TracerStringEncoding::TwoByte);
    408      if (!ensureScratchBufferSize(scratchBuffer, length * sizeof(char16_t))) {
    409        return false;
    410      }
    411      readBytes(reinterpret_cast<uint8_t*>(scratchBuffer.begin()),
    412                length * sizeof(char16_t));
    413 
    414      // Non-surrogate-paired single UTF-16 code unit maps to 1 to 3 UTF-8
    415      // code units. Surrogate paired UTF-16 code units map to 4 to 6 UTF-8
    416      // code units.
    417      size_t reserveLength = length * 3 + 1;
    418      if (!stringBuffer.reserve(stringBuffer.length() + reserveLength)) {
    419        return false;
    420      }
    421      char* writePtr = stringBuffer.end();
    422 
    423      size_t convertedLength = mozilla::ConvertUtf16toUtf8(
    424          mozilla::Span<const char16_t>(
    425              reinterpret_cast<char16_t*>(scratchBuffer.begin()), length),
    426          mozilla::Span<char>(writePtr, reserveLength));
    427      writePtr[convertedLength] = 0;
    428 
    429      // We reserved above, which just grows the capacity but not the length.
    430      // This just commits the exact length increase.
    431      if (!stringBuffer.growByUninitialized(convertedLength + 1)) {
    432        return false;
    433      }
    434    }
    435 
    436    return true;
    437  }
    438 
    439  bool readSmallString(TracingScratchBuffer& scratchBuffer,
    440                       mozilla::Vector<char>& stringBuffer, size_t* index) {
    441    return readString<uint16_t, InlineStringEncoding::Yes>(scratchBuffer,
    442                                                           stringBuffer, index);
    443  }
    444 };
    445 
    446 // These sizes are to some degree picked out of a hat, and eventually it might
    447 // be nice to make them configurable. For reference, I measured it costing
    448 // 145MB to open gdocs and create an empty document, so 256MB is just some
    449 // extra wiggle room for complex use cases.
    450 using InlineDataBuffer = TracingBuffer<1 << 28>;
    451 
    452 // We include a separate buffer for value summaries, so that we can store them
    453 // contiguously and so we don't lose information from the inline data if a
    454 // script has a lot of large values for instance.
    455 using ValueDataBuffer = InlineDataBuffer;
    456 
    457 // The size for the out of line data is much smaller, so I just picked a size
    458 // that was much smaller but big enough that I didn't see us running out of it
    459 // when playing around on various complex apps. Again, it would be great in the
    460 // future for this to be configurable.
    461 using OutOfLineDataBuffer = TracingBuffer<1 << 22>;
    462 
    463 class ValueSummaries {
    464  ValueDataBuffer* valueData_ = nullptr;
    465  OutOfLineDataBuffer* outOfLineData_ = nullptr;
    466 
    467  friend struct ::JS_TracerSummaryWriter;
    468 
    469 public:
    470  // Sometimes we write ValueSummarys as nested properties of other
    471  // ValueSummarys. This enum is used to indicate that in code when necessary.
    472  // (This value is not written into the serialized format, and should instead
    473  // be tracked by the reader)
    474  enum class IsNested { No, Yes };
    475 
    476  void init(ValueDataBuffer* valueData, OutOfLineDataBuffer* outOfLineData) {
    477    valueData_ = valueData;
    478    outOfLineData_ = outOfLineData;
    479  }
    480 
    481  bool writeValue(JSContext* cx, JS::Handle<JS::Value> val, IsNested nested);
    482 
    483  // valueBufferIndex will hold the index at which we wrote the arguments into
    484  // the valueData_ buffer
    485  bool writeArguments(JSContext* cx, AbstractFramePtr frame,
    486                      uint64_t* valueBufferIndex);
    487 
    488  // Unrolls the underlying ring buffer into a contiguous, compacted buffer
    489  // and puts it into the context's valueBuffer field.
    490  bool populateOutputBuffer(JS::ExecutionTrace::TracedJSContext& context);
    491 
    492  // If ringBufferIndex is still valid, translates it into an index into the
    493  // output buffer. Otherwise, this returns
    494  // JS::ExecutionTrace::EXPIRED_VALUES_MAGIC.
    495  int32_t getOutputBufferIndex(uint64_t ringBufferIndex);
    496 
    497  void writeHeader(JS::ValueType type, uint8_t flags);
    498  bool writeShapeSummary(JSContext* cx, JS::Handle<NativeShape*> shape);
    499 
    500  // Only writes the class name
    501  bool writeMinimalShapeSummary(JSContext* cx, JS::Handle<Shape*> shape);
    502 
    503  void writeObjectHeader(JS::ObjectSummary::Kind kind, uint8_t flags);
    504 
    505  bool writeObject(JSContext* cx, JS::Handle<JSObject*> obj, IsNested nested);
    506 
    507  bool writeFunctionSummary(JSContext* cx, JS::Handle<JSFunction*> fn,
    508                            IsNested nested);
    509  bool writeArrayObjectSummary(JSContext* cx, JS::Handle<ArrayObject*> array,
    510                               IsNested nested);
    511  bool writeSetObjectSummary(JSContext* cx, JS::Handle<SetObject*> set,
    512                             IsNested nested);
    513  bool writeMapObjectSummary(JSContext* cx, JS::Handle<MapObject*> map,
    514                             IsNested nested);
    515  bool writeErrorObjectSummary(JSContext* cx, JS::Handle<JSObject*> obj,
    516                               JS::Handle<ErrorObject*> error, IsNested nested);
    517  bool writeGenericOrWrappedPrimitiveObjectSummary(
    518      JSContext* cx, JS::Handle<NativeObject*> nobj, IsNested nested);
    519  bool writeExternalObjectSummary(JSContext* cx, JS::Handle<NativeObject*> nobj,
    520                                  IsNested nested);
    521 
    522  bool writeStringLikeValue(JSContext* cx, JS::ValueType valueType,
    523                            JS::Handle<JSString*> str);
    524 };
    525 
    526 // An ExecutionTracer is responsible for recording JS execution while it is
    527 // enabled to a set of ring buffers, and providing that information as a JS
    528 // object when requested. See Debugger.md (collectNativeTrace) for more details.
    529 class ExecutionTracer {
    530 private:
    531  // The fields below should only be accessed while we hold the lock.
    532  static Mutex globalInstanceLock MOZ_UNANNOTATED;
    533  static mozilla::Vector<ExecutionTracer*> globalInstances;
    534 
    535  // The buffers below should only be accessed while we hold the lock.
    536  Mutex bufferLock_ MOZ_UNANNOTATED;
    537 
    538  // This holds the actual entries, one for each push or pop of a frame or label
    539  InlineDataBuffer inlineData_;
    540 
    541  // This holds data that may be duplicated across entries, like script URLs or
    542  // function names. This should generally be much smaller in terms of raw
    543  // bytes. Note however that we can still wrap around this buffer and lose
    544  // entries - the system is best effort, and the consumer must accomodate the
    545  // fact that entries from inlineData_ may reference expired data from
    546  // outOfLineData_
    547  OutOfLineDataBuffer outOfLineData_;
    548 
    549  // This holds summaries of various values recorded during tracing. Currently
    550  // this only contains values for function arguments. TODO: Add support for
    551  // function return values.
    552  ValueDataBuffer valueData_;
    553 
    554  // This is just an ID that allows the profiler to easily correlate the trace
    555  // for a given context with the correct thread in the output profile.
    556  // We're operating on the assumption that there is one JSContext per thread,
    557  // which should be true enough for our uses in Firefox, but doesn't have to
    558  // be true everywhere.
    559  mozilla::baseprofiler::BaseProfilerThreadId threadId_;
    560 
    561  // This is a helper class for writing value data to the valueData_ and
    562  // outOfLineData_ buffers. It holds pointers to those two buffers and houses
    563  // all of the logic for writing the value summaries themselves.
    564  ValueSummaries valueSummaries_;
    565 
    566  // When we encounter an error during tracing, we write one final Error entry
    567  // and suspend tracing indefinitely. This allows the consumer to get some
    568  // information about what led up to the error, while preventing any
    569  // additional future overhead. An alternative to this approach would be to
    570  // clean up all of our buffers on error, but since the user must have elected
    571  // to turn on tracing, we assume that they would rather have a greater chance
    572  // of more information about what led up to the error rather than a greater
    573  // chance of avoiding a crash due to OOM.
    574  void handleError(JSContext* cx);
    575 
    576  void writeScriptUrl(ScriptSource* scriptSource);
    577 
    578  // Writes an atom into the outOfLineData_, associating it with the specified
    579  // id. In practice, `id` comes from an atom id inside a cache in the
    580  // JSContext which is incremented each time a new atom is registered and
    581  // cleared when tracing is done.
    582  bool writeAtom(JSContext* cx, JS::Handle<JSAtom*> atom, uint32_t id);
    583  bool writeFunctionFrame(JSContext* cx, AbstractFramePtr frame);
    584 
    585  // The below functions read data from the inlineData_ and outOfLineData_ ring
    586  // buffers into structs to be consumed by clients of the
    587  // JS_TracerSnapshotTrace API.
    588  bool readFunctionFrame(JS::ExecutionTrace::EventKind kind,
    589                         JS::ExecutionTrace::TracedEvent& event);
    590  bool readLabel(JS::ExecutionTrace::EventKind kind,
    591                 JS::ExecutionTrace::TracedEvent& event,
    592                 TracingScratchBuffer& scratchBuffer,
    593                 mozilla::Vector<char>& stringBuffer);
    594  bool readInlineEntry(mozilla::Vector<JS::ExecutionTrace::TracedEvent>& events,
    595                       TracingScratchBuffer& scratchBuffer,
    596                       mozilla::Vector<char>& stringBuffer);
    597  bool readOutOfLineEntry(
    598      mozilla::HashMap<uint32_t, size_t>& scriptUrls,
    599      mozilla::HashMap<uint32_t, size_t>& atoms,
    600      mozilla::Vector<JS::ExecutionTrace::ShapeSummary>& shapes,
    601      TracingScratchBuffer& scratchBuffer, mozilla::Vector<char>& stringBuffer);
    602  bool readInlineEntries(
    603      mozilla::Vector<JS::ExecutionTrace::TracedEvent>& events,
    604      TracingScratchBuffer& scratchBuffer, mozilla::Vector<char>& stringBuffer);
    605  bool readOutOfLineEntries(
    606      mozilla::HashMap<uint32_t, size_t>& scriptUrls,
    607      mozilla::HashMap<uint32_t, size_t>& atoms,
    608      mozilla::Vector<JS::ExecutionTrace::ShapeSummary>& shapes,
    609      TracingScratchBuffer& scratchBuffer, mozilla::Vector<char>& stringBuffer);
    610 
    611 public:
    612  ExecutionTracer() : bufferLock_(mutexid::ExecutionTracerInstanceLock) {}
    613 
    614  ~ExecutionTracer() {
    615    LockGuard<Mutex> guard(globalInstanceLock);
    616 
    617    globalInstances.eraseIfEqual(this);
    618  }
    619 
    620  mozilla::baseprofiler::BaseProfilerThreadId threadId() const {
    621    return threadId_;
    622  }
    623 
    624  bool init() {
    625    LockGuard<Mutex> guard(globalInstanceLock);
    626    LockGuard<Mutex> guard2(bufferLock_);
    627 
    628    threadId_ = mozilla::baseprofiler::profiler_current_thread_id();
    629 
    630    if (!inlineData_.init()) {
    631      return false;
    632    }
    633    if (!outOfLineData_.init()) {
    634      return false;
    635    }
    636    if (!valueData_.init()) {
    637      return false;
    638    }
    639 
    640    if (!globalInstances.append(this)) {
    641      return false;
    642    }
    643 
    644    valueSummaries_.init(&valueData_, &outOfLineData_);
    645 
    646    return true;
    647  }
    648 
    649  void onEnterFrame(JSContext* cx, AbstractFramePtr frame);
    650  void onLeaveFrame(JSContext* cx, AbstractFramePtr frame);
    651 
    652  template <typename CharType, JS::TracerStringEncoding Encoding>
    653  void onEnterLabel(const CharType* eventType);
    654  template <typename CharType, JS::TracerStringEncoding Encoding>
    655  void onLeaveLabel(const CharType* eventType);
    656 
    657  // Reads the execution trace from the underlying ring buffers and outputs it
    658  // into a native struct. For more information about this struct, see
    659  // js/public/Debug.h
    660  bool getNativeTrace(JS::ExecutionTrace::TracedJSContext& context,
    661                      TracingScratchBuffer& scratchBuffer,
    662                      mozilla::Vector<char>& stringBuffer);
    663 
    664  // Calls getNativeTrace for every JSContext in the process, populating the
    665  // provided ExecutionTrace with the result.
    666  static bool getNativeTraceForAllContexts(JS::ExecutionTrace& trace);
    667 };
    668 
    669 }  // namespace js
    670 
    671 #endif /* debugger_ExecutionTracer_h */