tor-browser

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

BaseProfileJSONWriter.h (20877B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #ifndef BASEPROFILEJSONWRITER_H
      7 #define BASEPROFILEJSONWRITER_H
      8 
      9 #include "mozilla/FailureLatch.h"
     10 #include "mozilla/HashFunctions.h"
     11 #include "mozilla/HashTable.h"
     12 #include "mozilla/JSONWriter.h"
     13 #include "mozilla/Maybe.h"
     14 #include "mozilla/NotNull.h"
     15 #include "mozilla/ProgressLogger.h"
     16 #include "mozilla/TimeStamp.h"
     17 #include "mozilla/UniquePtrExtensions.h"
     18 #include "mozilla/Flow.h"
     19 
     20 #include <functional>
     21 #include <ostream>
     22 #include <string_view>
     23 #include <stdint.h>
     24 
     25 namespace mozilla {
     26 namespace baseprofiler {
     27 
     28 class SpliceableJSONWriter;
     29 
     30 // On average, profile JSONs are large enough such that we want to avoid
     31 // reallocating its buffer when expanding. Additionally, the contents of the
     32 // profile are not accessed until the profile is entirely written. For these
     33 // reasons we use a chunked writer that keeps an array of chunks, which is
     34 // concatenated together after writing is finished.
     35 class ChunkedJSONWriteFunc final : public JSONWriteFunc, public FailureLatch {
     36 public:
     37  friend class SpliceableJSONWriter;
     38 
     39  explicit ChunkedJSONWriteFunc(FailureLatch& aFailureLatch)
     40      : mFailureLatch(WrapNotNullUnchecked(&aFailureLatch)) {
     41    (void)AllocChunk(kChunkSize);
     42  }
     43 
     44  [[nodiscard]] bool IsEmpty() const {
     45    MOZ_ASSERT_IF(!mChunkPtr, !mChunkEnd && mChunkList.length() == 0 &&
     46                                  mChunkLengths.length() == 0);
     47    return !mChunkPtr;
     48  }
     49 
     50  // Length of data written so far, excluding null terminator.
     51  [[nodiscard]] size_t Length() const {
     52    MOZ_ASSERT(mChunkLengths.length() == mChunkList.length());
     53    size_t totalLen = 0;
     54    for (size_t i = 0; i < mChunkLengths.length(); i++) {
     55      MOZ_ASSERT(strlen(mChunkList[i].get()) == mChunkLengths[i]);
     56      totalLen += mChunkLengths[i];
     57    }
     58    return totalLen;
     59  }
     60 
     61  void Write(const Span<const char>& aStr) final {
     62    if (Failed()) {
     63      return;
     64    }
     65 
     66    MOZ_ASSERT(mChunkPtr >= mChunkList.back().get() && mChunkPtr <= mChunkEnd);
     67    MOZ_ASSERT(mChunkEnd >= mChunkList.back().get() + mChunkLengths.back());
     68    MOZ_ASSERT(*mChunkPtr == '\0');
     69 
     70    // Most strings to be written are small, but subprocess profiles (e.g.,
     71    // from the content process in e10s) may be huge. If the string is larger
     72    // than a chunk, allocate its own chunk.
     73    char* newPtr;
     74    if (aStr.size() >= kChunkSize) {
     75      if (!AllocChunk(aStr.size() + 1)) {
     76        return;
     77      }
     78      newPtr = mChunkPtr + aStr.size();
     79    } else {
     80      newPtr = mChunkPtr + aStr.size();
     81      if (newPtr >= mChunkEnd) {
     82        if (!AllocChunk(kChunkSize)) {
     83          return;
     84        }
     85        newPtr = mChunkPtr + aStr.size();
     86      }
     87    }
     88 
     89    memcpy(mChunkPtr, aStr.data(), aStr.size());
     90    *newPtr = '\0';
     91    mChunkPtr = newPtr;
     92    mChunkLengths.back() += aStr.size();
     93  }
     94 
     95  [[nodiscard]] bool CopyDataIntoLazilyAllocatedBuffer(
     96      const std::function<char*(size_t)>& aAllocator) const {
     97    // Request a buffer for the full content plus a null terminator.
     98    if (Failed()) {
     99      return false;
    100    }
    101 
    102    char* ptr = aAllocator(Length() + 1);
    103 
    104    if (!ptr) {
    105      // Failed to allocate memory.
    106      return false;
    107    }
    108 
    109    for (size_t i = 0; i < mChunkList.length(); i++) {
    110      size_t len = mChunkLengths[i];
    111      memcpy(ptr, mChunkList[i].get(), len);
    112      ptr += len;
    113    }
    114    *ptr = '\0';
    115    return true;
    116  }
    117 
    118  [[nodiscard]] UniquePtr<char[]> CopyData() const {
    119    UniquePtr<char[]> c;
    120    if (!CopyDataIntoLazilyAllocatedBuffer([&](size_t allocationSize) {
    121          c = MakeUnique<char[]>(allocationSize);
    122          return c.get();
    123        })) {
    124      // Something went wrong, make sure the returned pointer is null even if
    125      // the allocation happened.
    126      c = nullptr;
    127    }
    128    return c;
    129  }
    130 
    131  void Take(ChunkedJSONWriteFunc&& aOther) {
    132    SetFailureFrom(aOther);
    133    if (Failed()) {
    134      return;
    135    }
    136 
    137    for (size_t i = 0; i < aOther.mChunkList.length(); i++) {
    138      MOZ_ALWAYS_TRUE(mChunkLengths.append(aOther.mChunkLengths[i]));
    139      MOZ_ALWAYS_TRUE(mChunkList.append(std::move(aOther.mChunkList[i])));
    140    }
    141    mChunkPtr = mChunkList.back().get() + mChunkLengths.back();
    142    mChunkEnd = mChunkPtr;
    143    aOther.Clear();
    144  }
    145 
    146  FAILURELATCH_IMPL_PROXY(*mFailureLatch)
    147 
    148  // Change the failure latch to be used here, and if the previous latch was
    149  // already in failure state, set that failure in the new latch.
    150  // This allows using this WriteFunc in isolation, before attempting to bring
    151  // it into another operation group with its own FailureLatch.
    152  void ChangeFailureLatchAndForwardState(FailureLatch& aFailureLatch) {
    153    aFailureLatch.SetFailureFrom(*this);
    154    mFailureLatch = WrapNotNullUnchecked(&aFailureLatch);
    155  }
    156 
    157 private:
    158  void Clear() {
    159    mChunkPtr = nullptr;
    160    mChunkEnd = nullptr;
    161    mChunkList.clear();
    162    mChunkLengths.clear();
    163  }
    164 
    165  void ClearAndSetFailure(std::string aFailure) {
    166    Clear();
    167    SetFailure(std::move(aFailure));
    168  }
    169 
    170  [[nodiscard]] bool ClearAndSetFailureAndFalse(std::string aFailure) {
    171    ClearAndSetFailure(std::move(aFailure));
    172    return false;
    173  }
    174 
    175  [[nodiscard]] bool AllocChunk(size_t aChunkSize) {
    176    if (Failed()) {
    177      if (mChunkPtr) {
    178        // FailureLatch is in failed state, but chunks have not been cleared yet
    179        // (error must have happened elsewhere).
    180        Clear();
    181      }
    182      return false;
    183    }
    184 
    185    MOZ_ASSERT(mChunkLengths.length() == mChunkList.length());
    186    UniquePtr<char[]> newChunk = MakeUniqueFallible<char[]>(aChunkSize);
    187    if (!newChunk) {
    188      return ClearAndSetFailureAndFalse(
    189          "OOM in ChunkedJSONWriteFunc::AllocChunk allocating new chunk");
    190    }
    191    mChunkPtr = newChunk.get();
    192    mChunkEnd = mChunkPtr + aChunkSize;
    193    *mChunkPtr = '\0';
    194    if (!mChunkLengths.append(0)) {
    195      return ClearAndSetFailureAndFalse(
    196          "OOM in ChunkedJSONWriteFunc::AllocChunk appending length");
    197    }
    198    if (!mChunkList.append(std::move(newChunk))) {
    199      return ClearAndSetFailureAndFalse(
    200          "OOM in ChunkedJSONWriteFunc::AllocChunk appending new chunk");
    201    }
    202    return true;
    203  }
    204 
    205  static const size_t kChunkSize = 4096 * 512;
    206 
    207  // Pointer for writing inside the current chunk.
    208  //
    209  // The current chunk is always at the back of mChunkList, i.e.,
    210  // mChunkList.back() <= mChunkPtr <= mChunkEnd.
    211  char* mChunkPtr = nullptr;
    212 
    213  // Pointer to the end of the current chunk.
    214  //
    215  // The current chunk is always at the back of mChunkList, i.e.,
    216  // mChunkEnd >= mChunkList.back() + mChunkLengths.back().
    217  char* mChunkEnd = nullptr;
    218 
    219  // List of chunks and their lengths.
    220  //
    221  // For all i, the length of the string in mChunkList[i] is
    222  // mChunkLengths[i].
    223  Vector<UniquePtr<char[]>> mChunkList;
    224  Vector<size_t> mChunkLengths;
    225 
    226  NotNull<FailureLatch*> mFailureLatch;
    227 };
    228 
    229 struct OStreamJSONWriteFunc final : public JSONWriteFunc {
    230  explicit OStreamJSONWriteFunc(std::ostream& aStream) : mStream(aStream) {}
    231 
    232  void Write(const Span<const char>& aStr) final {
    233    std::string_view sv(aStr.data(), aStr.size());
    234    mStream << sv;
    235  }
    236 
    237  std::ostream& mStream;
    238 };
    239 
    240 class UniqueJSONStrings;
    241 
    242 class SpliceableJSONWriter : public JSONWriter, public FailureLatch {
    243 public:
    244  SpliceableJSONWriter(JSONWriteFunc& aWriter, FailureLatch& aFailureLatch)
    245      : JSONWriter(aWriter, JSONWriter::SingleLineStyle),
    246        mFailureLatch(WrapNotNullUnchecked(&aFailureLatch)) {}
    247 
    248  SpliceableJSONWriter(UniquePtr<JSONWriteFunc> aWriter,
    249                       FailureLatch& aFailureLatch)
    250      : JSONWriter(std::move(aWriter), JSONWriter::SingleLineStyle),
    251        mFailureLatch(WrapNotNullUnchecked(&aFailureLatch)) {}
    252 
    253  void StartBareList() { StartCollection(scEmptyString, scEmptyString); }
    254 
    255  void EndBareList() { EndCollection(scEmptyString); }
    256 
    257  // Output a time (int64_t given in nanoseconds) in milliseconds. trim zeroes.
    258  // E.g.: 1'234'567'890 -> "1234.56789"
    259  void TimeI64NsProperty(const Span<const char>& aMaybePropertyName,
    260                         int64_t aTime_ns) {
    261    if (aTime_ns == 0) {
    262      Scalar(aMaybePropertyName, MakeStringSpan("0"));
    263      return;
    264    }
    265 
    266    static constexpr int64_t million = 1'000'000;
    267    const int64_t absNanos = std::abs(aTime_ns);
    268    const int64_t integerMilliseconds = absNanos / million;
    269    auto remainderNanoseconds = static_cast<uint32_t>(absNanos % million);
    270 
    271    // Plenty enough to fit INT64_MIN (-9223372036854775808).
    272    static constexpr size_t DIGITS_MAX = 23;
    273    char buf[DIGITS_MAX + 1];
    274    int len =
    275        snprintf(buf, DIGITS_MAX, (aTime_ns >= 0) ? "%" PRIu64 : "-%" PRIu64,
    276                 integerMilliseconds);
    277    if (remainderNanoseconds != 0) {
    278      buf[len++] = '.';
    279      // Output up to 6 fractional digits. Exit early if the rest would
    280      // be trailing zeros.
    281      uint32_t powerOfTen = static_cast<uint32_t>(million / 10);
    282      for (;;) {
    283        auto digit = remainderNanoseconds / powerOfTen;
    284        buf[len++] = '0' + static_cast<char>(digit);
    285        remainderNanoseconds %= powerOfTen;
    286        if (remainderNanoseconds == 0) {
    287          break;
    288        }
    289        powerOfTen /= 10;
    290        if (powerOfTen == 0) {
    291          break;
    292        }
    293      }
    294    }
    295 
    296    Scalar(aMaybePropertyName, Span<const char>(buf, len));
    297  }
    298 
    299  // Output a (double) time in milliseconds, with at best nanosecond precision.
    300  void TimeDoubleMsProperty(const Span<const char>& aMaybePropertyName,
    301                            double aTime_ms) {
    302    const double dTime_ns = aTime_ms * 1'000'000.0;
    303    // Make sure it's well within int64_t range.
    304    // 2^63 nanoseconds is almost 300 years; these times are relative to
    305    // firefox startup, this should be enough for most uses.
    306    if (dTime_ns >= 0.0) {
    307      MOZ_RELEASE_ASSERT(dTime_ns < double(INT64_MAX - 1));
    308    } else {
    309      MOZ_RELEASE_ASSERT(dTime_ns > double(INT64_MIN + 2));
    310    }
    311    // Round to nearest integer nanosecond. The conversion to integer truncates
    312    // the fractional part, so first we need to push it 0.5 away from zero.
    313    const int64_t iTime_ns =
    314        (dTime_ns >= 0.0) ? int64_t(dTime_ns + 0.5) : int64_t(dTime_ns - 0.5);
    315    TimeI64NsProperty(aMaybePropertyName, iTime_ns);
    316  }
    317 
    318  // Output a (double) time in milliseconds, with at best nanosecond precision.
    319  void TimeDoubleMsElement(double aTime_ms) {
    320    TimeDoubleMsProperty(nullptr, aTime_ms);
    321  }
    322 
    323  // This function must be used to correctly stream timestamps in profiles.
    324  // Null timestamps don't output anything.
    325  void TimeProperty(const Span<const char>& aMaybePropertyName,
    326                    const TimeStamp& aTime) {
    327    if (!aTime.IsNull()) {
    328      TimeDoubleMsProperty(
    329          aMaybePropertyName,
    330          (aTime - TimeStamp::ProcessCreation()).ToMilliseconds());
    331    }
    332  }
    333 
    334  // JSON doesn't support 64bit integers so we encode them as hex strings
    335  static std::array<char, 17> HexString(uint64_t aId) {
    336    std::array<char, 17> buf = {};
    337    static const char* hex_digits = "0123456789abcdef";
    338    for (int i = 0; i < 16; i++) {
    339      buf[i] = hex_digits[(aId >> (60 - i * 4)) & 0xf];
    340    }
    341    buf[16] = '0';  // null terminate the string
    342    return buf;
    343  }
    344 
    345  // We store flows as strings because JS can't handle 64 bit numbers in JSON
    346  void FlowProperty(const Span<const char>& aName, Flow aFlow) {
    347    UniqueStringProperty(aName, HexString(aFlow.Id()));
    348  }
    349 
    350  void NullElements(uint32_t aCount) {
    351    for (uint32_t i = 0; i < aCount; i++) {
    352      NullElement();
    353    }
    354  }
    355 
    356  void Splice(const Span<const char>& aStr) {
    357    Separator();
    358    WriteFunc().Write(aStr);
    359    mNeedComma[mDepth] = true;
    360  }
    361 
    362  void Splice(const char* aStr, size_t aLen) {
    363    Separator();
    364    WriteFunc().Write(Span<const char>(aStr, aLen));
    365    mNeedComma[mDepth] = true;
    366  }
    367 
    368  // Splice the given JSON directly in, without quoting.
    369  void SplicedJSONProperty(const Span<const char>& aMaybePropertyName,
    370                           const Span<const char>& aJsonValue) {
    371    Scalar(aMaybePropertyName, aJsonValue);
    372  }
    373 
    374  void CopyAndSplice(const ChunkedJSONWriteFunc& aFunc) {
    375    Separator();
    376    for (size_t i = 0; i < aFunc.mChunkList.length(); i++) {
    377      WriteFunc().Write(
    378          Span<const char>(aFunc.mChunkList[i].get(), aFunc.mChunkLengths[i]));
    379    }
    380    mNeedComma[mDepth] = true;
    381  }
    382 
    383  // Takes the chunks from aFunc and write them. If move is not possible
    384  // (e.g., using OStreamJSONWriteFunc), aFunc's chunks are copied and its
    385  // storage cleared.
    386  virtual void TakeAndSplice(ChunkedJSONWriteFunc&& aFunc) {
    387    Separator();
    388    for (size_t i = 0; i < aFunc.mChunkList.length(); i++) {
    389      WriteFunc().Write(
    390          Span<const char>(aFunc.mChunkList[i].get(), aFunc.mChunkLengths[i]));
    391    }
    392    aFunc.mChunkPtr = nullptr;
    393    aFunc.mChunkEnd = nullptr;
    394    aFunc.mChunkList.clear();
    395    aFunc.mChunkLengths.clear();
    396    mNeedComma[mDepth] = true;
    397  }
    398 
    399  // Set (or reset) the pointer to a UniqueJSONStrings.
    400  void SetUniqueStrings(UniqueJSONStrings& aUniqueStrings) {
    401    MOZ_RELEASE_ASSERT(!mUniqueStrings);
    402    mUniqueStrings = &aUniqueStrings;
    403  }
    404 
    405  // Set (or reset) the pointer to a UniqueJSONStrings.
    406  void ResetUniqueStrings() {
    407    MOZ_RELEASE_ASSERT(mUniqueStrings);
    408    mUniqueStrings = nullptr;
    409  }
    410 
    411  // Add `aStr` to the unique-strings list (if not already there), and write its
    412  // index as a named object property.
    413  inline void UniqueStringProperty(const Span<const char>& aName,
    414                                   const Span<const char>& aStr);
    415 
    416  // Add `aStr` to the unique-strings list (if not already there), and write its
    417  // index as an array element.
    418  inline void UniqueStringElement(const Span<const char>& aStr);
    419 
    420  // THe following functions override JSONWriter functions non-virtually. The
    421  // goal is to try and prevent calls that specify a style, which would be
    422  // ignored anyway because the whole thing is single-lined. It's fine if some
    423  // calls still make it through a `JSONWriter&`, no big deal.
    424  void Start() { JSONWriter::Start(); }
    425  void StartArrayProperty(const Span<const char>& aName) {
    426    JSONWriter::StartArrayProperty(aName);
    427  }
    428  template <size_t N>
    429  void StartArrayProperty(const char (&aName)[N]) {
    430    JSONWriter::StartArrayProperty(Span<const char>(aName, N));
    431  }
    432  void StartArrayElement() { JSONWriter::StartArrayElement(); }
    433  void StartObjectProperty(const Span<const char>& aName) {
    434    JSONWriter::StartObjectProperty(aName);
    435  }
    436  template <size_t N>
    437  void StartObjectProperty(const char (&aName)[N]) {
    438    JSONWriter::StartObjectProperty(Span<const char>(aName, N));
    439  }
    440  void StartObjectElement() { JSONWriter::StartObjectElement(); }
    441 
    442  FAILURELATCH_IMPL_PROXY(*mFailureLatch)
    443 
    444 protected:
    445  NotNull<FailureLatch*> mFailureLatch;
    446 
    447 private:
    448  UniqueJSONStrings* mUniqueStrings = nullptr;
    449 };
    450 
    451 class SpliceableChunkedJSONWriter final : public SpliceableJSONWriter {
    452 public:
    453  explicit SpliceableChunkedJSONWriter(FailureLatch& aFailureLatch)
    454      : SpliceableJSONWriter(MakeUnique<ChunkedJSONWriteFunc>(aFailureLatch),
    455                             aFailureLatch) {}
    456 
    457  // Access the ChunkedJSONWriteFunc as reference-to-const, usually to copy data
    458  // out.
    459  const ChunkedJSONWriteFunc& ChunkedWriteFunc() const {
    460    return ChunkedWriteFuncRef();
    461  }
    462 
    463  // Access the ChunkedJSONWriteFunc as rvalue-reference, usually to take its
    464  // data out. This writer shouldn't be used anymore after this.
    465  ChunkedJSONWriteFunc&& TakeChunkedWriteFunc() {
    466    ChunkedJSONWriteFunc& ref = ChunkedWriteFuncRef();
    467 #ifdef DEBUG
    468    mTaken = true;
    469 #endif  //
    470    return std::move(ref);
    471  }
    472 
    473  // Adopts the chunks from aFunc without copying.
    474  void TakeAndSplice(ChunkedJSONWriteFunc&& aFunc) override {
    475    MOZ_ASSERT(!mTaken);
    476    Separator();
    477    ChunkedWriteFuncRef().Take(std::move(aFunc));
    478    mNeedComma[mDepth] = true;
    479  }
    480 
    481  void ChangeFailureLatchAndForwardState(FailureLatch& aFailureLatch) {
    482    mFailureLatch = WrapNotNullUnchecked(&aFailureLatch);
    483    return ChunkedWriteFuncRef().ChangeFailureLatchAndForwardState(
    484        aFailureLatch);
    485  }
    486 
    487 private:
    488  const ChunkedJSONWriteFunc& ChunkedWriteFuncRef() const {
    489    MOZ_ASSERT(!mTaken);
    490    // The WriteFunc was non-fallibly allocated as a ChunkedJSONWriteFunc in the
    491    // only constructor above, so it's safe to cast to ChunkedJSONWriteFunc&.
    492    return static_cast<const ChunkedJSONWriteFunc&>(WriteFunc());
    493  }
    494 
    495  ChunkedJSONWriteFunc& ChunkedWriteFuncRef() {
    496    MOZ_ASSERT(!mTaken);
    497    // The WriteFunc was non-fallibly allocated as a ChunkedJSONWriteFunc in the
    498    // only constructor above, so it's safe to cast to ChunkedJSONWriteFunc&.
    499    return static_cast<ChunkedJSONWriteFunc&>(WriteFunc());
    500  }
    501 
    502 #ifdef DEBUG
    503  bool mTaken = false;
    504 #endif
    505 };
    506 
    507 class JSONSchemaWriter {
    508  JSONWriter& mWriter;
    509  uint32_t mIndex;
    510 
    511 public:
    512  explicit JSONSchemaWriter(JSONWriter& aWriter) : mWriter(aWriter), mIndex(0) {
    513    aWriter.StartObjectProperty("schema",
    514                                SpliceableJSONWriter::SingleLineStyle);
    515  }
    516 
    517  void WriteField(const Span<const char>& aName) {
    518    mWriter.IntProperty(aName, mIndex++);
    519  }
    520 
    521  template <size_t Np1>
    522  void WriteField(const char (&aName)[Np1]) {
    523    WriteField(Span<const char>(aName, Np1 - 1));
    524  }
    525 
    526  ~JSONSchemaWriter() { mWriter.EndObject(); }
    527 };
    528 
    529 // This class helps create an indexed list of unique strings, and inserts the
    530 // index as a JSON value. The collected list of unique strings can later be
    531 // inserted as a JSON array.
    532 // This can be useful for elements/properties with many repeated strings.
    533 //
    534 // With only JSONWriter w,
    535 // `w.WriteElement("a"); w.WriteElement("b"); w.WriteElement("a");`
    536 // when done inside a JSON array, will generate:
    537 // `["a", "b", "c"]`
    538 //
    539 // With UniqueStrings u,
    540 // `u.WriteElement(w, "a"); u.WriteElement(w, "b"); u.WriteElement(w, "a");`
    541 // when done inside a JSON array, will generate:
    542 // `[0, 1, 0]`
    543 // and later, `u.SpliceStringTableElements(w)` (inside a JSON array), will
    544 // output the corresponding indexed list of unique strings:
    545 // `["a", "b"]`
    546 class UniqueJSONStrings final : public FailureLatch {
    547 public:
    548  // Start an empty list of unique strings.
    549  MFBT_API explicit UniqueJSONStrings(FailureLatch& aFailureLatch);
    550 
    551  // Start with a copy of the strings from another list.
    552  MFBT_API UniqueJSONStrings(FailureLatch& aFailureLatch,
    553                             const UniqueJSONStrings& aOther,
    554                             ProgressLogger aProgressLogger);
    555 
    556  MFBT_API ~UniqueJSONStrings();
    557 
    558  // Add `aStr` to the list (if not already there), and write its index as a
    559  // named object property.
    560  void WriteProperty(SpliceableJSONWriter& aWriter,
    561                     const Span<const char>& aName,
    562                     const Span<const char>& aStr) {
    563    if (const Maybe<uint32_t> maybeIndex = GetOrAddIndex(aStr); maybeIndex) {
    564      aWriter.IntProperty(aName, *maybeIndex);
    565    } else {
    566      aWriter.SetFailureFrom(*this);
    567    }
    568  }
    569 
    570  // Add `aStr` to the list (if not already there), and write its index as an
    571  // array element.
    572  void WriteElement(SpliceableJSONWriter& aWriter,
    573                    const Span<const char>& aStr) {
    574    if (const Maybe<uint32_t> maybeIndex = GetOrAddIndex(aStr); maybeIndex) {
    575      aWriter.IntElement(*maybeIndex);
    576    } else if (!aWriter.Failed()) {
    577      aWriter.SetFailureFrom(*this);
    578    }
    579  }
    580 
    581  // Splice all collected unique strings into an array. This should only be done
    582  // once, and then this UniqueStrings shouldn't be used anymore.
    583  MFBT_API void SpliceStringTableElements(SpliceableJSONWriter& aWriter);
    584 
    585  FAILURELATCH_IMPL_PROXY(mStringTableWriter)
    586 
    587  void ChangeFailureLatchAndForwardState(FailureLatch& aFailureLatch) {
    588    mStringTableWriter.ChangeFailureLatchAndForwardState(aFailureLatch);
    589  }
    590 
    591 private:
    592  MFBT_API void ClearAndSetFailure(std::string aFailure);
    593 
    594  // If `aStr` is already listed, return its index.
    595  // Otherwise add it to the list and return the new index.
    596  MFBT_API Maybe<uint32_t> GetOrAddIndex(const Span<const char>& aStr);
    597 
    598  SpliceableChunkedJSONWriter mStringTableWriter;
    599  HashMap<HashNumber, uint32_t> mStringHashToIndexMap;
    600 };
    601 
    602 void SpliceableJSONWriter::UniqueStringProperty(const Span<const char>& aName,
    603                                                const Span<const char>& aStr) {
    604  MOZ_RELEASE_ASSERT(mUniqueStrings);
    605  mUniqueStrings->WriteProperty(*this, aName, aStr);
    606 }
    607 
    608 // Add `aStr` to the list (if not already there), and write its index as an
    609 // array element.
    610 void SpliceableJSONWriter::UniqueStringElement(const Span<const char>& aStr) {
    611  MOZ_RELEASE_ASSERT(mUniqueStrings);
    612  mUniqueStrings->WriteElement(*this, aStr);
    613 }
    614 
    615 }  // namespace baseprofiler
    616 }  // namespace mozilla
    617 
    618 #endif  // BASEPROFILEJSONWRITER_H