tor-browser

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

JSON.cpp (64460B)


      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 "builtin/JSON.h"
      8 
      9 #include "mozilla/CheckedInt.h"
     10 #include "mozilla/Range.h"
     11 #include "mozilla/ScopeExit.h"
     12 #include "mozilla/Variant.h"
     13 
     14 #include <algorithm>
     15 
     16 #include "jsnum.h"
     17 #include "jstypes.h"
     18 
     19 #include "builtin/Array.h"
     20 #include "builtin/BigInt.h"
     21 #include "builtin/ParseRecordObject.h"
     22 #include "builtin/RawJSONObject.h"
     23 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
     24 #include "js/friend/StackLimits.h"    // js::AutoCheckRecursionLimit
     25 #include "js/Object.h"                // JS::GetBuiltinClass
     26 #include "js/Prefs.h"                 // JS::Prefs
     27 #include "js/ProfilingCategory.h"
     28 #include "js/PropertySpec.h"
     29 #include "js/StableStringChars.h"
     30 #include "js/TypeDecls.h"
     31 #include "js/Value.h"
     32 #include "util/StringBuilder.h"
     33 #include "vm/BooleanObject.h"       // js::BooleanObject
     34 #include "vm/EqualityOperations.h"  // js::SameValue
     35 #include "vm/Interpreter.h"
     36 #include "vm/Iteration.h"
     37 #include "vm/JSAtomUtils.h"  // ToAtom
     38 #include "vm/JSContext.h"
     39 #include "vm/JSObject.h"
     40 #include "vm/JSONParser.h"
     41 #include "vm/NativeObject.h"
     42 #include "vm/NumberObject.h"  // js::NumberObject
     43 #include "vm/PlainObject.h"   // js::PlainObject
     44 #include "vm/StringObject.h"  // js::StringObject
     45 #include "builtin/Array-inl.h"
     46 #include "vm/GeckoProfiler-inl.h"
     47 #include "vm/JSAtomUtils-inl.h"  // AtomToId, PrimitiveValueToId, IndexToId, IdToString,
     48 #include "vm/NativeObject-inl.h"
     49 
     50 using namespace js;
     51 
     52 using mozilla::AsVariant;
     53 using mozilla::CheckedInt;
     54 using mozilla::Maybe;
     55 using mozilla::RangedPtr;
     56 using mozilla::Variant;
     57 
     58 using JS::AutoStableStringChars;
     59 
     60 /* https://262.ecma-international.org/14.0/#sec-quotejsonstring
     61 * Requires that the destination has enough space allocated for src after
     62 * escaping (that is, `2 + 6 * (srcEnd - srcBegin)` characters).
     63 */
     64 template <typename SrcCharT, typename DstCharT>
     65 static MOZ_ALWAYS_INLINE RangedPtr<DstCharT> InfallibleQuoteJSONString(
     66    RangedPtr<const SrcCharT> srcBegin, RangedPtr<const SrcCharT> srcEnd,
     67    RangedPtr<DstCharT> dstPtr) {
     68  // Maps characters < 256 to the value that must follow the '\\' in the quoted
     69  // string. Entries with 'u' are handled as \\u00xy, and entries with 0 are not
     70  // escaped in any way. Characters >= 256 are all assumed to be unescaped.
     71  static const Latin1Char escapeLookup[256] = {
     72      // clang-format off
     73        'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'b', 't',
     74        'n', 'u', 'f', 'r', 'u', 'u', 'u', 'u', 'u', 'u',
     75        'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u',
     76        'u', 'u', 0,   0,  '\"', 0,   0,   0,   0,   0,
     77        0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
     78        0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
     79        0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
     80        0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
     81        0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
     82        0,   0,  '\\', // rest are all zeros
     83      // clang-format on
     84  };
     85 
     86  /* Step 1. */
     87  *dstPtr++ = '"';
     88 
     89  auto ToLowerHex = [](uint8_t u) {
     90    MOZ_ASSERT(u <= 0xF);
     91    return "0123456789abcdef"[u];
     92  };
     93 
     94  /* Step 2. */
     95  while (srcBegin != srcEnd) {
     96    const SrcCharT c = *srcBegin++;
     97 
     98    // Handle the Latin-1 cases.
     99    if (MOZ_LIKELY(c < sizeof(escapeLookup))) {
    100      Latin1Char escaped = escapeLookup[c];
    101 
    102      // Directly copy non-escaped code points.
    103      if (escaped == 0) {
    104        *dstPtr++ = c;
    105        continue;
    106      }
    107 
    108      // Escape the rest, elaborating Unicode escapes when needed.
    109      *dstPtr++ = '\\';
    110      *dstPtr++ = escaped;
    111      if (escaped == 'u') {
    112        *dstPtr++ = '0';
    113        *dstPtr++ = '0';
    114 
    115        uint8_t x = c >> 4;
    116        MOZ_ASSERT(x < 10);
    117        *dstPtr++ = '0' + x;
    118 
    119        *dstPtr++ = ToLowerHex(c & 0xF);
    120      }
    121 
    122      continue;
    123    }
    124 
    125    // Non-ASCII non-surrogates are directly copied.
    126    if (!unicode::IsSurrogate(c)) {
    127      *dstPtr++ = c;
    128      continue;
    129    }
    130 
    131    // So too for complete surrogate pairs.
    132    if (MOZ_LIKELY(unicode::IsLeadSurrogate(c) && srcBegin < srcEnd &&
    133                   unicode::IsTrailSurrogate(*srcBegin))) {
    134      *dstPtr++ = c;
    135      *dstPtr++ = *srcBegin++;
    136      continue;
    137    }
    138 
    139    // But lone surrogates are Unicode-escaped.
    140    char32_t as32 = char32_t(c);
    141    *dstPtr++ = '\\';
    142    *dstPtr++ = 'u';
    143    *dstPtr++ = ToLowerHex(as32 >> 12);
    144    *dstPtr++ = ToLowerHex((as32 >> 8) & 0xF);
    145    *dstPtr++ = ToLowerHex((as32 >> 4) & 0xF);
    146    *dstPtr++ = ToLowerHex(as32 & 0xF);
    147  }
    148 
    149  /* Steps 3-4. */
    150  *dstPtr++ = '"';
    151  return dstPtr;
    152 }
    153 
    154 template <typename SrcCharT, typename DstCharT>
    155 static size_t QuoteJSONStringHelper(const JSLinearString& linear,
    156                                    StringBuilder& sb, size_t sbOffset) {
    157  size_t len = linear.length();
    158 
    159  JS::AutoCheckCannotGC nogc;
    160  RangedPtr<const SrcCharT> srcBegin{linear.chars<SrcCharT>(nogc), len};
    161  RangedPtr<DstCharT> dstBegin{sb.begin<DstCharT>(), sb.begin<DstCharT>(),
    162                               sb.end<DstCharT>()};
    163  RangedPtr<DstCharT> dstEnd =
    164      InfallibleQuoteJSONString(srcBegin, srcBegin + len, dstBegin + sbOffset);
    165 
    166  return dstEnd - dstBegin;
    167 }
    168 
    169 static bool QuoteJSONString(JSContext* cx, StringBuilder& sb, JSString* str) {
    170  JSLinearString* linear = str->ensureLinear(cx);
    171  if (!linear) {
    172    return false;
    173  }
    174 
    175  if (linear->hasTwoByteChars() && !sb.ensureTwoByteChars()) {
    176    return false;
    177  }
    178 
    179  // We resize the backing buffer to the maximum size we could possibly need,
    180  // write the escaped string into it, and shrink it back to the size we ended
    181  // up needing.
    182 
    183  size_t len = linear->length();
    184  size_t sbInitialLen = sb.length();
    185 
    186  CheckedInt<size_t> reservedLen = CheckedInt<size_t>(len) * 6 + 2;
    187  if (MOZ_UNLIKELY(!reservedLen.isValid())) {
    188    ReportAllocationOverflow(cx);
    189    return false;
    190  }
    191 
    192  if (!sb.growByUninitialized(reservedLen.value())) {
    193    return false;
    194  }
    195 
    196  size_t newSize;
    197 
    198  if (linear->hasTwoByteChars()) {
    199    newSize =
    200        QuoteJSONStringHelper<char16_t, char16_t>(*linear, sb, sbInitialLen);
    201  } else if (sb.isUnderlyingBufferLatin1()) {
    202    newSize = QuoteJSONStringHelper<Latin1Char, Latin1Char>(*linear, sb,
    203                                                            sbInitialLen);
    204  } else {
    205    newSize =
    206        QuoteJSONStringHelper<Latin1Char, char16_t>(*linear, sb, sbInitialLen);
    207  }
    208 
    209  sb.shrinkTo(newSize);
    210 
    211  return true;
    212 }
    213 
    214 namespace {
    215 
    216 using ObjectVector = GCVector<JSObject*, 8>;
    217 
    218 class StringifyContext {
    219 public:
    220  StringifyContext(JSContext* cx, StringBuilder& sb, const StringBuilder& gap,
    221                   HandleObject replacer, const RootedIdVector& propertyList,
    222                   bool maybeSafely)
    223      : sb(sb),
    224        gap(gap),
    225        replacer(cx, replacer),
    226        stack(cx, ObjectVector(cx)),
    227        propertyList(propertyList),
    228        depth(0),
    229        maybeSafely(maybeSafely) {
    230    MOZ_ASSERT_IF(maybeSafely, !replacer);
    231    MOZ_ASSERT_IF(maybeSafely, gap.empty());
    232  }
    233 
    234  StringBuilder& sb;
    235  const StringBuilder& gap;
    236  RootedObject replacer;
    237  Rooted<ObjectVector> stack;
    238  const RootedIdVector& propertyList;
    239  uint32_t depth;
    240  bool maybeSafely;
    241 };
    242 
    243 } /* anonymous namespace */
    244 
    245 static bool SerializeJSONProperty(JSContext* cx, const Value& v,
    246                                  StringifyContext* scx);
    247 
    248 static bool WriteIndent(StringifyContext* scx, uint32_t limit) {
    249  if (!scx->gap.empty()) {
    250    if (!scx->sb.append('\n')) {
    251      return false;
    252    }
    253 
    254    if (scx->gap.isUnderlyingBufferLatin1()) {
    255      for (uint32_t i = 0; i < limit; i++) {
    256        if (!scx->sb.append(scx->gap.rawLatin1Begin(),
    257                            scx->gap.rawLatin1End())) {
    258          return false;
    259        }
    260      }
    261    } else {
    262      for (uint32_t i = 0; i < limit; i++) {
    263        if (!scx->sb.append(scx->gap.rawTwoByteBegin(),
    264                            scx->gap.rawTwoByteEnd())) {
    265          return false;
    266        }
    267      }
    268    }
    269  }
    270 
    271  return true;
    272 }
    273 
    274 namespace {
    275 
    276 template <typename KeyType>
    277 class KeyStringifier {};
    278 
    279 template <>
    280 class KeyStringifier<uint32_t> {
    281 public:
    282  static JSString* toString(JSContext* cx, uint32_t index) {
    283    return IndexToString(cx, index);
    284  }
    285 };
    286 
    287 template <>
    288 class KeyStringifier<HandleId> {
    289 public:
    290  static JSString* toString(JSContext* cx, HandleId id) {
    291    return IdToString(cx, id);
    292  }
    293 };
    294 
    295 } /* anonymous namespace */
    296 
    297 /*
    298 * https://262.ecma-international.org/14.0/#sec-serializejsonproperty, steps
    299 * 2-4, extracted to enable preprocessing of property values when stringifying
    300 * objects in SerializeJSONObject.
    301 */
    302 template <typename KeyType>
    303 static bool PreprocessValue(JSContext* cx, HandleObject holder, KeyType key,
    304                            MutableHandleValue vp, StringifyContext* scx) {
    305  // We don't want to do any preprocessing here if scx->maybeSafely,
    306  // since the stuff we do here can have side-effects.
    307  if (scx->maybeSafely) {
    308    return true;
    309  }
    310 
    311  RootedString keyStr(cx);
    312 
    313  // Step 2. Modified by BigInt spec 6.1 to check for a toJSON method on the
    314  // BigInt prototype when the value is a BigInt, and to pass the BigInt
    315  // primitive value as receiver.
    316  if (vp.isObject() || vp.isBigInt()) {
    317    RootedValue toJSON(cx);
    318    RootedObject obj(cx, JS::ToObject(cx, vp));
    319    if (!obj) {
    320      return false;
    321    }
    322 
    323    if (!GetProperty(cx, obj, vp, cx->names().toJSON, &toJSON)) {
    324      return false;
    325    }
    326 
    327    if (IsCallable(toJSON)) {
    328      keyStr = KeyStringifier<KeyType>::toString(cx, key);
    329      if (!keyStr) {
    330        return false;
    331      }
    332 
    333      RootedValue arg0(cx, StringValue(keyStr));
    334      if (!js::Call(cx, toJSON, vp, arg0, vp)) {
    335        return false;
    336      }
    337    }
    338  }
    339 
    340  /* Step 3. */
    341  if (scx->replacer && scx->replacer->isCallable()) {
    342    MOZ_ASSERT(holder != nullptr,
    343               "holder object must be present when replacer is callable");
    344 
    345    if (!keyStr) {
    346      keyStr = KeyStringifier<KeyType>::toString(cx, key);
    347      if (!keyStr) {
    348        return false;
    349      }
    350    }
    351 
    352    RootedValue arg0(cx, StringValue(keyStr));
    353    RootedValue replacerVal(cx, ObjectValue(*scx->replacer));
    354    if (!js::Call(cx, replacerVal, holder, arg0, vp, vp)) {
    355      return false;
    356    }
    357  }
    358 
    359  /* Step 4. */
    360  if (vp.get().isObject()) {
    361    RootedObject obj(cx, &vp.get().toObject());
    362 
    363    ESClass cls;
    364    if (!JS::GetBuiltinClass(cx, obj, &cls)) {
    365      return false;
    366    }
    367 
    368    if (cls == ESClass::Number) {
    369      double d;
    370      if (!ToNumber(cx, vp, &d)) {
    371        return false;
    372      }
    373      vp.setNumber(d);
    374    } else if (cls == ESClass::String) {
    375      JSString* str = ToStringSlow<CanGC>(cx, vp);
    376      if (!str) {
    377        return false;
    378      }
    379      vp.setString(str);
    380    } else if (cls == ESClass::Boolean || cls == ESClass::BigInt) {
    381      if (!Unbox(cx, obj, vp)) {
    382        return false;
    383      }
    384    }
    385  }
    386 
    387  return true;
    388 }
    389 
    390 /*
    391 * Determines whether a value which has passed by
    392 * https://262.ecma-international.org/14.0/#sec-serializejsonproperty steps
    393 * 1-4's gauntlet will result in SerializeJSONProperty returning |undefined|.
    394 * This function is used to properly omit properties resulting in such values
    395 * when stringifying objects, while properly stringifying such properties as
    396 * null when they're encountered in arrays.
    397 */
    398 static inline bool IsFilteredValue(const Value& v) {
    399  MOZ_ASSERT_IF(v.isMagic(), v.isMagic(JS_ELEMENTS_HOLE));
    400  return v.isUndefined() || v.isSymbol() || v.isMagic() || IsCallable(v);
    401 }
    402 
    403 class CycleDetector {
    404 public:
    405  CycleDetector(StringifyContext* scx, HandleObject obj)
    406      : stack_(&scx->stack), obj_(obj), appended_(false) {}
    407 
    408  MOZ_ALWAYS_INLINE bool foundCycle(JSContext* cx) {
    409    JSObject* obj = obj_;
    410    for (JSObject* obj2 : stack_) {
    411      if (MOZ_UNLIKELY(obj == obj2)) {
    412        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    413                                  JSMSG_JSON_CYCLIC_VALUE);
    414        return false;
    415      }
    416    }
    417    appended_ = stack_.append(obj);
    418    return appended_;
    419  }
    420 
    421  ~CycleDetector() {
    422    if (MOZ_LIKELY(appended_)) {
    423      MOZ_ASSERT(stack_.back() == obj_);
    424      stack_.popBack();
    425    }
    426  }
    427 
    428 private:
    429  MutableHandle<ObjectVector> stack_;
    430  HandleObject obj_;
    431  bool appended_;
    432 };
    433 
    434 static inline JSString* MaybeGetRawJSON(JSContext* cx, JSObject* obj) {
    435  auto* unwrappedObj = obj->maybeUnwrapIf<js::RawJSONObject>();
    436  if (!unwrappedObj) {
    437    return nullptr;
    438  }
    439  JSAutoRealm ar(cx, unwrappedObj);
    440 
    441  JSString* rawJSON = unwrappedObj->rawJSON(cx);
    442  MOZ_ASSERT(rawJSON);
    443  return rawJSON;
    444 }
    445 
    446 /* https://262.ecma-international.org/14.0/#sec-serializejsonobject */
    447 static bool SerializeJSONObject(JSContext* cx, HandleObject obj,
    448                                StringifyContext* scx) {
    449  /*
    450   * This method implements the SerializeJSONObject algorithm, but:
    451   *
    452   *   * The algorithm is somewhat reformulated to allow the final string to
    453   *     be streamed into a single buffer, rather than be created and copied
    454   *     into place incrementally as the algorithm specifies it.  This
    455   *     requires moving portions of the SerializeJSONProperty call in 8a into
    456   *     this algorithm (and in SerializeJSONArray as well).
    457   */
    458 
    459  MOZ_ASSERT_IF(scx->maybeSafely, obj->is<PlainObject>());
    460 
    461  /* Steps 1-2, 11. */
    462  CycleDetector detect(scx, obj);
    463  if (!detect.foundCycle(cx)) {
    464    return false;
    465  }
    466 
    467  if (!scx->sb.append('{')) {
    468    return false;
    469  }
    470 
    471  /* Steps 5-7. */
    472  Maybe<RootedIdVector> ids;
    473  const RootedIdVector* props;
    474  if (scx->replacer && !scx->replacer->isCallable()) {
    475    // NOTE: We can't assert |IsArray(scx->replacer)| because the replacer
    476    //       might have been a revocable proxy to an array.  Such a proxy
    477    //       satisfies |IsArray|, but any side effect of JSON.stringify
    478    //       could revoke the proxy so that |!IsArray(scx->replacer)|.  See
    479    //       bug 1196497.
    480    props = &scx->propertyList;
    481  } else {
    482    MOZ_ASSERT_IF(scx->replacer, scx->propertyList.length() == 0);
    483    ids.emplace(cx);
    484    if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY, ids.ptr())) {
    485      return false;
    486    }
    487    props = ids.ptr();
    488  }
    489 
    490  /* My kingdom for not-quite-initialized-from-the-start references. */
    491  const RootedIdVector& propertyList = *props;
    492 
    493  /* Steps 8-10, 13. */
    494  bool wroteMember = false;
    495  RootedId id(cx);
    496  for (size_t i = 0, len = propertyList.length(); i < len; i++) {
    497    if (!CheckForInterrupt(cx)) {
    498      return false;
    499    }
    500 
    501    /*
    502     * Steps 8a-8b.  Note that the call to SerializeJSONProperty is broken up
    503     * into 1) getting the property; 2) processing for toJSON, calling the
    504     * replacer, and handling boxed Number/String/Boolean objects; 3) filtering
    505     * out values which process to |undefined|, and 4) stringifying all values
    506     * which pass the filter.
    507     */
    508    id = propertyList[i];
    509    RootedValue outputValue(cx);
    510 #ifdef DEBUG
    511    if (scx->maybeSafely) {
    512      PropertyResult prop;
    513      if (!NativeLookupOwnPropertyNoResolve(cx, &obj->as<NativeObject>(), id,
    514                                            &prop)) {
    515        return false;
    516      }
    517      MOZ_ASSERT(prop.isNativeProperty() &&
    518                 prop.propertyInfo().isDataDescriptor());
    519    }
    520 #endif  // DEBUG
    521    RootedValue objValue(cx, ObjectValue(*obj));
    522    if (!GetProperty(cx, obj, objValue, id, &outputValue)) {
    523      return false;
    524    }
    525 
    526    if (!PreprocessValue(cx, obj, HandleId(id), &outputValue, scx)) {
    527      return false;
    528    }
    529    if (IsFilteredValue(outputValue)) {
    530      continue;
    531    }
    532 
    533    /* Output a comma unless this is the first member to write. */
    534    if (wroteMember && !scx->sb.append(',')) {
    535      return false;
    536    }
    537    wroteMember = true;
    538 
    539    if (!WriteIndent(scx, scx->depth)) {
    540      return false;
    541    }
    542 
    543    JSString* s = IdToString(cx, id);
    544    if (!s) {
    545      return false;
    546    }
    547 
    548    if (!QuoteJSONString(cx, scx->sb, s) || !scx->sb.append(':') ||
    549        !(scx->gap.empty() || scx->sb.append(' ')) ||
    550        !SerializeJSONProperty(cx, outputValue, scx)) {
    551      return false;
    552    }
    553  }
    554 
    555  if (wroteMember && !WriteIndent(scx, scx->depth - 1)) {
    556    return false;
    557  }
    558 
    559  return scx->sb.append('}');
    560 }
    561 
    562 // For JSON.stringify and JSON.parse with a reviver function, we need to know
    563 // the length of an object for which JS::IsArray returned true. This must be
    564 // either an ArrayObject or a proxy wrapping one.
    565 static MOZ_ALWAYS_INLINE bool GetLengthPropertyForArrayLike(JSContext* cx,
    566                                                            HandleObject obj,
    567                                                            uint32_t* lengthp) {
    568  if (MOZ_LIKELY(obj->is<ArrayObject>())) {
    569    *lengthp = obj->as<ArrayObject>().length();
    570    return true;
    571  }
    572 
    573  MOZ_ASSERT(obj->is<ProxyObject>());
    574 
    575  uint64_t len = 0;
    576  if (!GetLengthProperty(cx, obj, &len)) {
    577    return false;
    578  }
    579 
    580  // A scripted proxy wrapping an array can return a length value larger than
    581  // UINT32_MAX. Stringification will likely report an OOM in this case. Match
    582  // other JS engines and report an early error in this case, although
    583  // technically this is observable, for example when stringifying with a
    584  // replacer function.
    585  if (len > UINT32_MAX) {
    586    ReportAllocationOverflow(cx);
    587    return false;
    588  }
    589 
    590  *lengthp = uint32_t(len);
    591  return true;
    592 }
    593 
    594 /* https://262.ecma-international.org/14.0/#sec-serializejsonarray */
    595 static bool SerializeJSONArray(JSContext* cx, HandleObject obj,
    596                               StringifyContext* scx) {
    597  /*
    598   * This method implements the SerializeJSONArray algorithm, but:
    599   *
    600   *   * The algorithm is somewhat reformulated to allow the final string to
    601   *     be streamed into a single buffer, rather than be created and copied
    602   *     into place incrementally as the algorithm specifies it.  This
    603   *     requires moving portions of the SerializeJSONProperty call in 8a into
    604   *     this algorithm (and in SerializeJSONObject as well).
    605   */
    606 
    607  /* Steps 1-2, 11. */
    608  CycleDetector detect(scx, obj);
    609  if (!detect.foundCycle(cx)) {
    610    return false;
    611  }
    612 
    613  if (!scx->sb.append('[')) {
    614    return false;
    615  }
    616 
    617  /* Step 6. */
    618  uint32_t length;
    619  if (!GetLengthPropertyForArrayLike(cx, obj, &length)) {
    620    return false;
    621  }
    622 
    623  /* Steps 7-10. */
    624  if (length != 0) {
    625    /* Steps 4, 10b(i). */
    626    if (!WriteIndent(scx, scx->depth)) {
    627      return false;
    628    }
    629 
    630    /* Steps 7-10. */
    631    RootedValue outputValue(cx);
    632    for (uint32_t i = 0; i < length; i++) {
    633      if (!CheckForInterrupt(cx)) {
    634        return false;
    635      }
    636 
    637      /*
    638       * Steps 8a-8c.  Again note how the call to the spec's
    639       * SerializeJSONProperty method is broken up into getting the property,
    640       * running it past toJSON and the replacer and maybe unboxing, and
    641       * interpreting some values as |null| in separate steps.
    642       */
    643 #ifdef DEBUG
    644      if (scx->maybeSafely) {
    645        /*
    646         * Trying to do a JS_AlreadyHasOwnElement runs the risk of
    647         * hitting OOM on jsid creation.  Let's just assert sanity for
    648         * small enough indices.
    649         */
    650        MOZ_ASSERT(obj->is<ArrayObject>());
    651        MOZ_ASSERT(obj->is<NativeObject>());
    652        auto* nativeObj = &obj->as<NativeObject>();
    653        if (i <= PropertyKey::IntMax) {
    654          MOZ_ASSERT(
    655              nativeObj->containsDenseElement(i) != nativeObj->isIndexed(),
    656              "the array must either be small enough to remain "
    657              "fully dense (and otherwise un-indexed), *or* "
    658              "all its initially-dense elements were sparsified "
    659              "and the object is indexed");
    660        } else {
    661          MOZ_ASSERT(nativeObj->isIndexed());
    662        }
    663      }
    664 #endif
    665      if (!GetElement(cx, obj, i, &outputValue)) {
    666        return false;
    667      }
    668      if (!PreprocessValue(cx, obj, i, &outputValue, scx)) {
    669        return false;
    670      }
    671      if (IsFilteredValue(outputValue)) {
    672        if (!scx->sb.append("null")) {
    673          return false;
    674        }
    675      } else {
    676        if (!SerializeJSONProperty(cx, outputValue, scx)) {
    677          return false;
    678        }
    679      }
    680 
    681      /* Steps 3, 4, 10b(i). */
    682      if (i < length - 1) {
    683        if (!scx->sb.append(',')) {
    684          return false;
    685        }
    686        if (!WriteIndent(scx, scx->depth)) {
    687          return false;
    688        }
    689      }
    690    }
    691 
    692    /* Step 10(b)(iii). */
    693    if (!WriteIndent(scx, scx->depth - 1)) {
    694      return false;
    695    }
    696  }
    697 
    698  return scx->sb.append(']');
    699 }
    700 
    701 /* https://262.ecma-international.org/14.0/#sec-serializejsonproperty */
    702 static bool SerializeJSONProperty(JSContext* cx, const Value& v,
    703                                  StringifyContext* scx) {
    704  /* Step 12 must be handled by the caller. */
    705  MOZ_ASSERT(!IsFilteredValue(v));
    706 
    707  /*
    708   * This method implements the SerializeJSONProperty algorithm, but:
    709   *
    710   *   * We move property retrieval (step 1) into callers to stream the
    711   *     stringification process and avoid constantly copying strings.
    712   *   * We move the preprocessing in steps 2-4 into a helper function to
    713   *     allow both SerializeJSONObject and SerializeJSONArray to use this
    714   * method.  While SerializeJSONArray could use it without this move,
    715   * SerializeJSONObject must omit any |undefined|-valued property per so it
    716   * can't stream out a value using the SerializeJSONProperty method exactly as
    717   * defined by the spec.
    718   *   * We move step 12 into callers, again to ease streaming.
    719   */
    720 
    721  /* Step 8. */
    722  if (v.isString()) {
    723    return QuoteJSONString(cx, scx->sb, v.toString());
    724  }
    725 
    726  /* Step 5. */
    727  if (v.isNull()) {
    728    return scx->sb.append("null");
    729  }
    730 
    731  /* Steps 6-7. */
    732  if (v.isBoolean()) {
    733    return v.toBoolean() ? scx->sb.append("true") : scx->sb.append("false");
    734  }
    735 
    736  /* Step 9. */
    737  if (v.isNumber()) {
    738    if (v.isDouble()) {
    739      if (!std::isfinite(v.toDouble())) {
    740        MOZ_ASSERT(!scx->maybeSafely,
    741                   "input JS::ToJSONMaybeSafely must not include "
    742                   "reachable non-finite numbers");
    743        return scx->sb.append("null");
    744      }
    745    }
    746 
    747    return NumberValueToStringBuilder(v, scx->sb);
    748  }
    749 
    750  /* Step 10. */
    751  if (v.isBigInt()) {
    752    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    753                              JSMSG_BIGINT_NOT_SERIALIZABLE);
    754    return false;
    755  }
    756 
    757  AutoCheckRecursionLimit recursion(cx);
    758  if (!recursion.check(cx)) {
    759    return false;
    760  }
    761 
    762  /* Step 11. */
    763  MOZ_ASSERT(v.isObject());
    764  RootedObject obj(cx, &v.toObject());
    765 
    766  /* https://tc39.es/proposal-json-parse-with-source/#sec-serializejsonproperty
    767   * Step 4a.*/
    768  if (JSString* rawJSON = MaybeGetRawJSON(cx, obj)) {
    769    return scx->sb.append(rawJSON);
    770  }
    771 
    772  MOZ_ASSERT(
    773      !scx->maybeSafely || obj->is<PlainObject>() || obj->is<ArrayObject>(),
    774      "input to JS::ToJSONMaybeSafely must not include reachable "
    775      "objects that are neither arrays nor plain objects");
    776 
    777  scx->depth++;
    778  auto dec = mozilla::MakeScopeExit([&] { scx->depth--; });
    779 
    780  bool isArray;
    781  if (!IsArray(cx, obj, &isArray)) {
    782    return false;
    783  }
    784 
    785  return isArray ? SerializeJSONArray(cx, obj, scx)
    786                 : SerializeJSONObject(cx, obj, scx);
    787 }
    788 
    789 static bool CanFastStringifyObject(NativeObject* obj) {
    790  if (ClassCanHaveExtraEnumeratedProperties(obj->getClass())) {
    791    return false;
    792  }
    793 
    794  if (obj->is<ArrayObject>()) {
    795    // Arrays will look up all keys [0..length) so disallow anything that could
    796    // find those keys anywhere but in the dense elements.
    797    if (!IsPackedArray(obj) && ObjectMayHaveExtraIndexedProperties(obj)) {
    798      return false;
    799    }
    800  } else {
    801    // Non-Arrays will only look at own properties, but still disallow any
    802    // indexed properties other than in the dense elements because they would
    803    // require sorting.
    804    if (ObjectMayHaveExtraIndexedOwnProperties(obj)) {
    805      return false;
    806    }
    807  }
    808 
    809  // Only used for internal environment objects that should never be passed to
    810  // JSON.stringify.
    811  MOZ_ASSERT(!obj->getOpsLookupProperty());
    812 
    813  return true;
    814 }
    815 
    816 #define FOR_EACH_STRINGIFY_BAIL_REASON(MACRO) \
    817  MACRO(NO_REASON)                            \
    818  MACRO(INELIGIBLE_OBJECT)                    \
    819  MACRO(DEEP_RECURSION)                       \
    820  MACRO(NON_DATA_PROPERTY)                    \
    821  MACRO(TOO_MANY_PROPERTIES)                  \
    822  MACRO(BIGINT)                               \
    823  MACRO(API)                                  \
    824  MACRO(HAVE_REPLACER)                        \
    825  MACRO(HAVE_SPACE)                           \
    826  MACRO(PRIMITIVE)                            \
    827  MACRO(HAVE_TOJSON)                          \
    828  MACRO(IMPURE_LOOKUP)                        \
    829  MACRO(INTERRUPT)
    830 
    831 enum class BailReason : uint8_t {
    832 #define DECLARE_ENUM(name) name,
    833  FOR_EACH_STRINGIFY_BAIL_REASON(DECLARE_ENUM)
    834 #undef DECLARE_ENUM
    835 };
    836 
    837 static const char* DescribeStringifyBailReason(BailReason whySlow) {
    838  switch (whySlow) {
    839 #define ENUM_NAME(name)  \
    840  case BailReason::name: \
    841    return #name;
    842    FOR_EACH_STRINGIFY_BAIL_REASON(ENUM_NAME)
    843 #undef ENUM_NAME
    844    default:
    845      return "Unknown";
    846  }
    847 }
    848 
    849 // Iterator over all the dense elements of an object. Used
    850 // for both Arrays and non-Arrays.
    851 class DenseElementsIteratorForJSON {
    852  HeapSlotArray elements;
    853  uint32_t element;
    854 
    855  // Arrays can have a length less than getDenseInitializedLength(), in which
    856  // case the remaining Array elements are treated as UndefinedValue.
    857  uint32_t numElements;
    858  uint32_t length;
    859 
    860 public:
    861  explicit DenseElementsIteratorForJSON(NativeObject* nobj)
    862      : elements(nobj->getDenseElements()),
    863        element(0),
    864        numElements(nobj->getDenseInitializedLength()) {
    865    length = nobj->is<ArrayObject>() ? nobj->as<ArrayObject>().length()
    866                                     : numElements;
    867  }
    868 
    869  bool done() const { return element == length; }
    870 
    871  Value next() {
    872    // For Arrays, steps 6-8 of
    873    // https://262.ecma-international.org/14.0/#sec-serializejsonarray. For
    874    // non-Arrays, step 6a of
    875    // https://262.ecma-international.org/14.0/#sec-serializejsonobject
    876    // following the order from
    877    // https://262.ecma-international.org/14.0/#sec-ordinaryownpropertykeys
    878 
    879    MOZ_ASSERT(!done());
    880    auto i = element++;
    881    // Consider specializing the iterator for Arrays vs non-Arrays to avoid this
    882    // branch.
    883    return i < numElements ? elements.begin()[i] : UndefinedValue();
    884  }
    885 
    886  uint32_t getIndex() const { return element; }
    887 };
    888 
    889 // An iterator over the non-element properties of a Shape, returned in forward
    890 // (creation) order. Note that it is fallible, so after iteration is complete
    891 // isOverflowed() should be called to verify that the results are actually
    892 // complete.
    893 
    894 class ShapePropertyForwardIterNoGC {
    895  // Pointer to the current PropMap with length and an index within it.
    896  PropMap* map_;
    897  uint32_t mapLength_;
    898  uint32_t i_ = 0;
    899 
    900  // Stack of PropMaps to iterate through, oldest properties on top. The current
    901  // map (map_, above) is never on this stack.
    902  mozilla::Vector<PropMap*> stack_;
    903 
    904  const NativeShape* shape_;
    905 
    906  MOZ_ALWAYS_INLINE void settle() {
    907    while (true) {
    908      if (MOZ_UNLIKELY(i_ == mapLength_)) {
    909        i_ = 0;
    910        if (stack_.empty()) {
    911          mapLength_ = 0;  // Done
    912          return;
    913        }
    914        map_ = stack_.back();
    915        stack_.popBack();
    916        mapLength_ =
    917            stack_.empty() ? shape_->propMapLength() : PropMap::Capacity;
    918      } else if (MOZ_UNLIKELY(shape_->isDictionary() && !map_->hasKey(i_))) {
    919        // Dictionary maps can have "holes" for removed properties, so keep
    920        // going until we find a non-hole slot.
    921        i_++;
    922      } else {
    923        return;
    924      }
    925    }
    926  }
    927 
    928 public:
    929  explicit ShapePropertyForwardIterNoGC(NativeShape* shape) : shape_(shape) {
    930    // Set map_ to the PropMap containing the first property (the deepest map in
    931    // the previous() chain). Push pointers to all other PropMaps onto stack_.
    932    map_ = shape->propMap();
    933    if (!map_) {
    934      // No properties.
    935      i_ = mapLength_ = 0;
    936      return;
    937    }
    938    while (map_->hasPrevious()) {
    939      if (!stack_.append(map_)) {
    940        // Overflowed.
    941        i_ = mapLength_ = UINT32_MAX;
    942        return;
    943      }
    944      map_ = map_->asLinked()->previous();
    945    }
    946 
    947    // Set mapLength_ to the number of properties in map_ (including dictionary
    948    // holes, if any.)
    949    mapLength_ = stack_.empty() ? shape_->propMapLength() : PropMap::Capacity;
    950 
    951    settle();
    952  }
    953 
    954  bool done() const { return i_ == mapLength_; }
    955  bool isOverflowed() const { return i_ == UINT32_MAX; }
    956 
    957  void operator++(int) {
    958    MOZ_ASSERT(!done());
    959    i_++;
    960    settle();
    961  }
    962 
    963  PropertyInfoWithKey get() const {
    964    MOZ_ASSERT(!done());
    965    return map_->getPropertyInfoWithKey(i_);
    966  }
    967 
    968  PropertyInfoWithKey operator*() const { return get(); }
    969 
    970  // Fake pointer struct to make operator-> work.
    971  // See https://stackoverflow.com/a/52856349.
    972  struct FakePtr {
    973    PropertyInfoWithKey val_;
    974    const PropertyInfoWithKey* operator->() const { return &val_; }
    975  };
    976  FakePtr operator->() const { return {get()}; }
    977 };
    978 
    979 // Iterator over EnumerableOwnProperties
    980 // https://262.ecma-international.org/14.0/#sec-enumerableownproperties
    981 // that fails if it encounters any accessor properties, as they are not handled
    982 // by JSON FastSerializeJSONProperty, or if it sees too many properties on one
    983 // object.
    984 class OwnNonIndexKeysIterForJSON {
    985  ShapePropertyForwardIterNoGC shapeIter;
    986  bool done_ = false;
    987  BailReason fastFailed_ = BailReason::NO_REASON;
    988 
    989  void settle() {
    990    // Skip over any non-enumerable or Symbol properties, and permanently fail
    991    // if any enumerable non-data properties are encountered.
    992    for (; !shapeIter.done(); shapeIter++) {
    993      if (!shapeIter->enumerable()) {
    994        continue;
    995      }
    996      if (!shapeIter->isDataProperty()) {
    997        fastFailed_ = BailReason::NON_DATA_PROPERTY;
    998        done_ = true;
    999        return;
   1000      }
   1001      PropertyKey id = shapeIter->key();
   1002      if (!id.isSymbol()) {
   1003        return;
   1004      }
   1005    }
   1006    done_ = true;
   1007  }
   1008 
   1009 public:
   1010  explicit OwnNonIndexKeysIterForJSON(const NativeObject* nobj)
   1011      : shapeIter(nobj->shape()) {
   1012    if (MOZ_UNLIKELY(shapeIter.isOverflowed())) {
   1013      fastFailed_ = BailReason::TOO_MANY_PROPERTIES;
   1014      done_ = true;
   1015      return;
   1016    }
   1017    if (!nobj->hasEnumerableProperty()) {
   1018      // Non-Arrays with no enumerable properties can just be skipped.
   1019      MOZ_ASSERT(!nobj->is<ArrayObject>());
   1020      done_ = true;
   1021      return;
   1022    }
   1023    settle();
   1024  }
   1025 
   1026  bool done() const { return done_ || shapeIter.done(); }
   1027  BailReason cannotFastStringify() const { return fastFailed_; }
   1028 
   1029  PropertyInfoWithKey next() {
   1030    MOZ_ASSERT(!done());
   1031    PropertyInfoWithKey prop = shapeIter.get();
   1032    shapeIter++;
   1033    settle();
   1034    return prop;
   1035  }
   1036 };
   1037 
   1038 // Steps from https://262.ecma-international.org/14.0/#sec-serializejsonproperty
   1039 static bool EmitSimpleValue(JSContext* cx, StringBuilder& sb, const Value& v) {
   1040  /* Step 8. */
   1041  if (v.isString()) {
   1042    return QuoteJSONString(cx, sb, v.toString());
   1043  }
   1044 
   1045  /* Step 5. */
   1046  if (v.isNull()) {
   1047    return sb.append("null");
   1048  }
   1049 
   1050  /* Steps 6-7. */
   1051  if (v.isBoolean()) {
   1052    return v.toBoolean() ? sb.append("true") : sb.append("false");
   1053  }
   1054 
   1055  /* Step 9. */
   1056  if (v.isNumber()) {
   1057    if (v.isDouble()) {
   1058      if (!std::isfinite(v.toDouble())) {
   1059        return sb.append("null");
   1060      }
   1061    }
   1062 
   1063    return NumberValueToStringBuilder(v, sb);
   1064  }
   1065 
   1066  // Unrepresentable values.
   1067  if (v.isUndefined() || v.isMagic()) {
   1068    MOZ_ASSERT_IF(v.isMagic(), v.isMagic(JS_ELEMENTS_HOLE));
   1069    return sb.append("null");
   1070  }
   1071 
   1072  /* Step 10. */
   1073  MOZ_CRASH("should have validated printable simple value already");
   1074 }
   1075 
   1076 // https://262.ecma-international.org/14.0/#sec-serializejsonproperty step 8b
   1077 // where K is an integer index.
   1078 static bool EmitQuotedIndexColon(StringBuilder& sb, uint32_t index) {
   1079  Int32ToCStringBuf cbuf;
   1080  size_t cstrlen;
   1081  const char* cstr = ::Int32ToCString(&cbuf, index, &cstrlen);
   1082  if (!sb.reserve(sb.length() + 1 + cstrlen + 1 + 1)) {
   1083    return false;
   1084  }
   1085  sb.infallibleAppend('"');
   1086  sb.infallibleAppend(cstr, cstrlen);
   1087  sb.infallibleAppend('"');
   1088  sb.infallibleAppend(':');
   1089  return true;
   1090 }
   1091 
   1092 // Similar to PreprocessValue: replace the value with a simpler one to
   1093 // stringify, but also detect whether the value is compatible with the fast
   1094 // path. If not, bail out by setting *whySlow and returning true.
   1095 static bool PreprocessFastValue(JSContext* cx, Value* vp, StringifyContext* scx,
   1096                                BailReason* whySlow) {
   1097  MOZ_ASSERT(!scx->maybeSafely);
   1098 
   1099  // Steps are from
   1100  // https://262.ecma-international.org/14.0/#sec-serializejsonproperty
   1101 
   1102  // Disallow BigInts to avoid caring about BigInt.prototype.toJSON.
   1103  if (vp->isBigInt()) {
   1104    *whySlow = BailReason::BIGINT;
   1105    return true;
   1106  }
   1107 
   1108  if (!vp->isObject()) {
   1109    return true;
   1110  }
   1111 
   1112  if (!vp->toObject().is<NativeObject>()) {
   1113    *whySlow = BailReason::INELIGIBLE_OBJECT;
   1114    return true;
   1115  }
   1116 
   1117  // Step 2: lookup a .toJSON property (and bail if found).
   1118  NativeObject* obj = &vp->toObject().as<NativeObject>();
   1119  PropertyResult toJSON;
   1120  NativeObject* holder;
   1121  PropertyKey id = NameToId(cx->names().toJSON);
   1122  if (!NativeLookupPropertyInline<NoGC, LookupResolveMode::CheckMayResolve>(
   1123          cx, obj, id, &holder, &toJSON)) {
   1124    // Looking up this property would require a side effect.
   1125    *whySlow = BailReason::IMPURE_LOOKUP;
   1126    return true;
   1127  }
   1128  if (toJSON.isFound()) {
   1129    *whySlow = BailReason::HAVE_TOJSON;
   1130    return true;
   1131  }
   1132 
   1133  // Step 4: convert primitive wrapper objects to primitives. Disallowed for
   1134  // fast path.
   1135  if (obj->is<NumberObject>() || obj->is<StringObject>() ||
   1136      obj->is<BooleanObject>() || obj->is<BigIntObject>()) {
   1137    // Primitive wrapper objects can invoke arbitrary code when being coerced to
   1138    // their primitive values (eg via @@toStringTag).
   1139    *whySlow = BailReason::INELIGIBLE_OBJECT;
   1140    return true;
   1141  }
   1142 
   1143  if (obj->isCallable()) {
   1144    // Steps 11,12: Callable objects are treated as undefined.
   1145    vp->setUndefined();
   1146    return true;
   1147  }
   1148 
   1149  if (!CanFastStringifyObject(obj)) {
   1150    *whySlow = BailReason::INELIGIBLE_OBJECT;
   1151    return true;
   1152  }
   1153 
   1154  return true;
   1155 }
   1156 
   1157 // FastSerializeJSONProperty maintains an explicit stack to handle nested
   1158 // objects. For each object, first the dense elements are iterated, then the
   1159 // named properties (included sparse indexes, which will cause
   1160 // FastSerializeJSONProperty to bail out.)
   1161 //
   1162 // The iterators for each of those parts are not merged into a single common
   1163 // iterator because the interface is different for the two parts, and they are
   1164 // handled separately in the FastSerializeJSONProperty code.
   1165 struct FastStackEntry {
   1166  NativeObject* nobj;
   1167  Variant<DenseElementsIteratorForJSON, OwnNonIndexKeysIterForJSON> iter;
   1168  bool isArray;  // Cached nobj->is<ArrayObject>()
   1169 
   1170  // Given an object, a FastStackEntry starts with the dense elements. The
   1171  // caller is expected to inspect the variant to use it differently based on
   1172  // which iterator is active.
   1173  explicit FastStackEntry(NativeObject* obj)
   1174      : nobj(obj),
   1175        iter(AsVariant(DenseElementsIteratorForJSON(obj))),
   1176        isArray(obj->is<ArrayObject>()) {}
   1177 
   1178  // Called by Vector when moving data around.
   1179  FastStackEntry(FastStackEntry&& other) noexcept
   1180      : nobj(other.nobj), iter(std::move(other.iter)), isArray(other.isArray) {}
   1181 
   1182  // Move assignment, called when updating the `top` entry.
   1183  void operator=(FastStackEntry&& other) noexcept {
   1184    nobj = other.nobj;
   1185    iter = std::move(other.iter);
   1186    isArray = other.isArray;
   1187  }
   1188 
   1189  // Advance from dense elements to the named properties.
   1190  void advanceToProperties() {
   1191    iter = AsVariant(OwnNonIndexKeysIterForJSON(nobj));
   1192  }
   1193 };
   1194 
   1195 /* https://262.ecma-international.org/14.0/#sec-serializejsonproperty */
   1196 static bool FastSerializeJSONProperty(JSContext* cx, Handle<Value> v,
   1197                                      StringifyContext* scx,
   1198                                      BailReason* whySlow) {
   1199  MOZ_ASSERT(*whySlow == BailReason::NO_REASON);
   1200  MOZ_ASSERT(v.isObject());
   1201 
   1202  if (JSString* rawJSON = MaybeGetRawJSON(cx, &v.toObject())) {
   1203    return scx->sb.append(rawJSON);
   1204  }
   1205 
   1206  /*
   1207   * FastSerializeJSONProperty is an optimistic fast path for the
   1208   * SerializeJSONProperty algorithm that applies in limited situations. It
   1209   * falls back to SerializeJSONProperty() if:
   1210   *
   1211   *   * Any externally visible code attempts to run: getter, enumerate
   1212   *     hook, toJSON property.
   1213   *   * Sparse index found (this would require accumulating props and sorting.)
   1214   *   * Max stack depth is reached. (This will also detect self-referential
   1215   *     input.)
   1216   *
   1217   *  Algorithm:
   1218   *
   1219   *    stack = []
   1220   *    top = iter(obj)
   1221   *    wroteMember = false
   1222   *    OUTER: while true:
   1223   *      if !wroteMember:
   1224   *        emit("[" or "{")
   1225   *      while !top.done():
   1226   *        key, value = top.next()
   1227   *        if top is a non-Array and value is skippable:
   1228   *          continue
   1229   *        if wroteMember:
   1230   *          emit(",")
   1231   *        wroteMember = true
   1232   *        if value is object:
   1233   *          emit(key + ":") if top is iterating a non-Array
   1234   *          stack.push(top)
   1235   *          top <- value
   1236   *          wroteMember = false
   1237   *          continue OUTER
   1238   *        else:
   1239   *          emit(value) or emit(key + ":" + value)
   1240   *      emit("]" or "}")
   1241   *      if stack is empty: done!
   1242   *      top <- stack.pop()
   1243   *      wroteMember = true
   1244   *
   1245   * except:
   1246   *
   1247   *   * The `while !top.done()` loop is split into the dense element portion
   1248   *     and the slot portion. Each is iterated to completion before advancing
   1249   *     or finishing.
   1250   *
   1251   *   * For Arrays, the named properties are not output, but they are still
   1252   *     scanned to bail if any numeric keys are found that could be indexes.
   1253   */
   1254 
   1255  // FastSerializeJSONProperty will bail if an interrupt is requested in the
   1256  // middle of an operation, so handle any interrupts now before starting. Note:
   1257  // this can GC, but after this point nothing should be able to GC unless
   1258  // something fails, so rooting is unnecessary.
   1259  if (!CheckForInterrupt(cx)) {
   1260    return false;
   1261  }
   1262 
   1263  constexpr size_t MAX_STACK_DEPTH = 20;
   1264  Vector<FastStackEntry> stack(cx);
   1265  if (!stack.reserve(MAX_STACK_DEPTH - 1)) {
   1266    return false;
   1267  }
   1268  // Construct an iterator for the object,
   1269  // https://262.ecma-international.org/14.0/#sec-serializejsonobject step 6:
   1270  // EnumerableOwnPropertyNames or
   1271  // https://262.ecma-international.org/14.0/#sec-serializejsonarray step 7-8.
   1272  FastStackEntry top(&v.toObject().as<NativeObject>());
   1273  bool wroteMember = false;
   1274 
   1275  if (!CanFastStringifyObject(top.nobj)) {
   1276    *whySlow = BailReason::INELIGIBLE_OBJECT;
   1277    return true;
   1278  }
   1279 
   1280  while (true) {
   1281    if (!wroteMember) {
   1282      if (!scx->sb.append(top.isArray ? '[' : '{')) {
   1283        return false;
   1284      }
   1285    }
   1286 
   1287    if (top.iter.is<DenseElementsIteratorForJSON>()) {
   1288      auto& iter = top.iter.as<DenseElementsIteratorForJSON>();
   1289      bool nestedObject = false;
   1290      while (!iter.done()) {
   1291        // Interrupts can GC and we are working with unrooted pointers.
   1292        if (cx->hasPendingInterrupt(InterruptReason::CallbackUrgent) ||
   1293            cx->hasPendingInterrupt(InterruptReason::CallbackCanWait)) {
   1294          *whySlow = BailReason::INTERRUPT;
   1295          return true;
   1296        }
   1297 
   1298        uint32_t index = iter.getIndex();
   1299        Value val = iter.next();
   1300 
   1301        if (!PreprocessFastValue(cx, &val, scx, whySlow)) {
   1302          return false;
   1303        }
   1304        if (*whySlow != BailReason::NO_REASON) {
   1305          return true;
   1306        }
   1307        if (IsFilteredValue(val)) {
   1308          if (top.isArray) {
   1309            // Arrays convert unrepresentable values to "null".
   1310            val = UndefinedValue();
   1311          } else {
   1312            // Objects skip unrepresentable values.
   1313            continue;
   1314          }
   1315        }
   1316 
   1317        if (wroteMember && !scx->sb.append(',')) {
   1318          return false;
   1319        }
   1320        wroteMember = true;
   1321 
   1322        if (!top.isArray) {
   1323          if (!EmitQuotedIndexColon(scx->sb, index)) {
   1324            return false;
   1325          }
   1326        }
   1327 
   1328        if (val.isObject()) {
   1329          if (JSString* rawJSON = MaybeGetRawJSON(cx, &val.toObject())) {
   1330            if (!scx->sb.append(rawJSON)) {
   1331              return false;
   1332            }
   1333          } else {
   1334            if (stack.length() >= MAX_STACK_DEPTH - 1) {
   1335              *whySlow = BailReason::DEEP_RECURSION;
   1336              return true;
   1337            }
   1338            // Save the current iterator position on the stack and
   1339            // switch to processing the nested value.
   1340            stack.infallibleAppend(std::move(top));
   1341            top = FastStackEntry(&val.toObject().as<NativeObject>());
   1342            wroteMember = false;
   1343            nestedObject = true;  // Break out to the outer loop.
   1344            break;
   1345          }
   1346        } else if (!EmitSimpleValue(cx, scx->sb, val)) {
   1347          return false;
   1348        }
   1349      }
   1350 
   1351      if (nestedObject) {
   1352        continue;  // Break out to outer loop.
   1353      }
   1354 
   1355      MOZ_ASSERT(iter.done());
   1356      if (top.isArray) {
   1357        MOZ_ASSERT(!top.nobj->isIndexed() || IsPackedArray(top.nobj));
   1358      } else {
   1359        top.advanceToProperties();
   1360      }
   1361    }
   1362 
   1363    if (top.iter.is<OwnNonIndexKeysIterForJSON>()) {
   1364      auto& iter = top.iter.as<OwnNonIndexKeysIterForJSON>();
   1365      bool nesting = false;
   1366      while (!iter.done()) {
   1367        // Interrupts can GC and we are working with unrooted pointers.
   1368        if (cx->hasPendingInterrupt(InterruptReason::CallbackUrgent) ||
   1369            cx->hasPendingInterrupt(InterruptReason::CallbackCanWait)) {
   1370          *whySlow = BailReason::INTERRUPT;
   1371          return true;
   1372        }
   1373 
   1374        PropertyInfoWithKey prop = iter.next();
   1375 
   1376        // A non-Array with indexed elements would need to sort the indexes
   1377        // numerically, which this code does not support. These objects are
   1378        // skipped when obj->isIndexed(), so no index properties should be found
   1379        // here.
   1380        mozilla::DebugOnly<uint32_t> index = -1;
   1381        MOZ_ASSERT(!IdIsIndex(prop.key(), &index));
   1382 
   1383        Value val = top.nobj->getSlot(prop.slot());
   1384        if (!PreprocessFastValue(cx, &val, scx, whySlow)) {
   1385          return false;
   1386        }
   1387        if (*whySlow != BailReason::NO_REASON) {
   1388          return true;
   1389        }
   1390        if (IsFilteredValue(val)) {
   1391          // Undefined check in
   1392          // https://262.ecma-international.org/14.0/#sec-serializejsonobject
   1393          // step 8b, covering undefined, symbol
   1394          continue;
   1395        }
   1396 
   1397        if (wroteMember && !scx->sb.append(",")) {
   1398          return false;
   1399        }
   1400        wroteMember = true;
   1401 
   1402        MOZ_ASSERT(prop.key().isString());
   1403        if (!QuoteJSONString(cx, scx->sb, prop.key().toString())) {
   1404          return false;
   1405        }
   1406 
   1407        if (!scx->sb.append(':')) {
   1408          return false;
   1409        }
   1410        if (val.isObject()) {
   1411          if (JSString* rawJSON = MaybeGetRawJSON(cx, &val.toObject())) {
   1412            if (!scx->sb.append(rawJSON)) {
   1413              return false;
   1414            }
   1415          } else {
   1416            if (stack.length() >= MAX_STACK_DEPTH - 1) {
   1417              *whySlow = BailReason::DEEP_RECURSION;
   1418              return true;
   1419            }
   1420            // Save the current iterator position on the stack and
   1421            // switch to processing the nested value.
   1422            stack.infallibleAppend(std::move(top));
   1423            top = FastStackEntry(&val.toObject().as<NativeObject>());
   1424            wroteMember = false;
   1425            nesting = true;  // Break out to the outer loop.
   1426            break;
   1427          }
   1428        } else if (!EmitSimpleValue(cx, scx->sb, val)) {
   1429          return false;
   1430        }
   1431      }
   1432      *whySlow = iter.cannotFastStringify();
   1433      if (*whySlow != BailReason::NO_REASON) {
   1434        return true;
   1435      }
   1436      if (nesting) {
   1437        continue;  // Break out to outer loop.
   1438      }
   1439      MOZ_ASSERT(iter.done());
   1440    }
   1441 
   1442    if (!scx->sb.append(top.isArray ? ']' : '}')) {
   1443      return false;
   1444    }
   1445    if (stack.empty()) {
   1446      return true;  // Success!
   1447    }
   1448    top = std::move(stack.back());
   1449 
   1450    stack.popBack();
   1451    wroteMember = true;
   1452  }
   1453 }
   1454 
   1455 /* https://262.ecma-international.org/14.0/#sec-json.stringify */
   1456 bool js::Stringify(JSContext* cx, MutableHandleValue vp, JSObject* replacer_,
   1457                   const Value& space_, StringBuilder& sb,
   1458                   StringifyBehavior stringifyBehavior) {
   1459  RootedObject replacer(cx, replacer_);
   1460  RootedValue space(cx, space_);
   1461 
   1462  MOZ_ASSERT_IF(stringifyBehavior == StringifyBehavior::RestrictedSafe,
   1463                space.isNull());
   1464  MOZ_ASSERT_IF(stringifyBehavior == StringifyBehavior::RestrictedSafe,
   1465                vp.isObject());
   1466  /**
   1467   * This uses MOZ_ASSERT, since it's actually asserting something jsapi
   1468   * consumers could get wrong, so needs a better error message.
   1469   */
   1470  MOZ_ASSERT(stringifyBehavior != StringifyBehavior::RestrictedSafe ||
   1471                 vp.toObject().is<PlainObject>() ||
   1472                 vp.toObject().is<ArrayObject>(),
   1473             "input to JS::ToJSONMaybeSafely must be a plain object or array");
   1474 
   1475  /* Step 5. */
   1476  RootedIdVector propertyList(cx);
   1477  BailReason whySlow = BailReason::NO_REASON;
   1478  if (stringifyBehavior == StringifyBehavior::SlowOnly ||
   1479      stringifyBehavior == StringifyBehavior::RestrictedSafe) {
   1480    whySlow = BailReason::API;
   1481  }
   1482  if (replacer) {
   1483    whySlow = BailReason::HAVE_REPLACER;
   1484    bool isArray;
   1485    if (replacer->isCallable()) {
   1486      /* Step 5a(i): use replacer to transform values.  */
   1487    } else if (!IsArray(cx, replacer, &isArray)) {
   1488      return false;
   1489    } else if (isArray) {
   1490      /* Step 5b(ii). */
   1491 
   1492      /* Step 5b(ii)(2). */
   1493      uint32_t len;
   1494      if (!GetLengthPropertyForArrayLike(cx, replacer, &len)) {
   1495        return false;
   1496      }
   1497 
   1498      // Cap the initial size to a moderately small value.  This avoids
   1499      // ridiculous over-allocation if an array with bogusly-huge length
   1500      // is passed in.  If we end up having to add elements past this
   1501      // size, the set will naturally resize to accommodate them.
   1502      const uint32_t MaxInitialSize = 32;
   1503      Rooted<GCHashSet<jsid>> idSet(
   1504          cx, GCHashSet<jsid>(cx, std::min(len, MaxInitialSize)));
   1505 
   1506      /* Step 5b(ii)(3). */
   1507      uint32_t k = 0;
   1508 
   1509      /* Step 5b(ii)(4). */
   1510      RootedValue item(cx);
   1511      for (; k < len; k++) {
   1512        if (!CheckForInterrupt(cx)) {
   1513          return false;
   1514        }
   1515 
   1516        /* Step 5b(ii)(4)(a-b). */
   1517        if (!GetElement(cx, replacer, k, &item)) {
   1518          return false;
   1519        }
   1520 
   1521        /* Step 5b(ii)(4)(c-g). */
   1522        RootedId id(cx);
   1523        if (item.isNumber() || item.isString()) {
   1524          if (!PrimitiveValueToId<CanGC>(cx, item, &id)) {
   1525            return false;
   1526          }
   1527        } else {
   1528          ESClass cls;
   1529          if (!GetClassOfValue(cx, item, &cls)) {
   1530            return false;
   1531          }
   1532 
   1533          if (cls != ESClass::String && cls != ESClass::Number) {
   1534            continue;
   1535          }
   1536 
   1537          JSAtom* atom = ToAtom<CanGC>(cx, item);
   1538          if (!atom) {
   1539            return false;
   1540          }
   1541 
   1542          id.set(AtomToId(atom));
   1543        }
   1544 
   1545        /* Step 5b(ii)(4)(g). */
   1546        auto p = idSet.lookupForAdd(id);
   1547        if (!p) {
   1548          /* Step 5b(ii)(4)(g)(i). */
   1549          if (!idSet.add(p, id) || !propertyList.append(id)) {
   1550            return false;
   1551          }
   1552        }
   1553      }
   1554    } else {
   1555      replacer = nullptr;
   1556    }
   1557  }
   1558 
   1559  /* Step 6. */
   1560  if (space.isObject()) {
   1561    RootedObject spaceObj(cx, &space.toObject());
   1562 
   1563    ESClass cls;
   1564    if (!JS::GetBuiltinClass(cx, spaceObj, &cls)) {
   1565      return false;
   1566    }
   1567 
   1568    if (cls == ESClass::Number) {
   1569      double d;
   1570      if (!ToNumber(cx, space, &d)) {
   1571        return false;
   1572      }
   1573      space = NumberValue(d);
   1574    } else if (cls == ESClass::String) {
   1575      JSString* str = ToStringSlow<CanGC>(cx, space);
   1576      if (!str) {
   1577        return false;
   1578      }
   1579      space = StringValue(str);
   1580    }
   1581  }
   1582 
   1583  StringBuilder gap(cx);
   1584 
   1585  if (space.isNumber()) {
   1586    /* Step 7. */
   1587    double d;
   1588    MOZ_ALWAYS_TRUE(ToInteger(cx, space, &d));
   1589    d = std::min(10.0, d);
   1590    if (d >= 1 && !gap.appendN(' ', uint32_t(d))) {
   1591      return false;
   1592    }
   1593  } else if (space.isString()) {
   1594    /* Step 8. */
   1595    JSLinearString* str = space.toString()->ensureLinear(cx);
   1596    if (!str) {
   1597      return false;
   1598    }
   1599    size_t len = std::min(size_t(10), str->length());
   1600    if (!gap.appendSubstring(str, 0, len)) {
   1601      return false;
   1602    }
   1603  } else {
   1604    /* Step 9. */
   1605    MOZ_ASSERT(gap.empty());
   1606  }
   1607  if (!gap.empty()) {
   1608    whySlow = BailReason::HAVE_SPACE;
   1609  }
   1610 
   1611  Rooted<PlainObject*> wrapper(cx);
   1612  RootedId emptyId(cx, NameToId(cx->names().empty_));
   1613  if (replacer && replacer->isCallable()) {
   1614    // We can skip creating the initial wrapper object if no replacer
   1615    // function is present.
   1616 
   1617    /* Step 10. */
   1618    wrapper = NewPlainObject(cx);
   1619    if (!wrapper) {
   1620      return false;
   1621    }
   1622 
   1623    /* Step 11. */
   1624    if (!NativeDefineDataProperty(cx, wrapper, emptyId, vp, JSPROP_ENUMERATE)) {
   1625      return false;
   1626    }
   1627  }
   1628 
   1629  /* Step 13. */
   1630  Rooted<JSAtom*> fastJSON(cx);
   1631  if (whySlow == BailReason::NO_REASON) {
   1632    MOZ_ASSERT(propertyList.empty());
   1633    MOZ_ASSERT(stringifyBehavior != StringifyBehavior::RestrictedSafe);
   1634    StringifyContext scx(cx, sb, gap, nullptr, propertyList, false);
   1635    if (!PreprocessFastValue(cx, vp.address(), &scx, &whySlow)) {
   1636      return false;
   1637    }
   1638    if (!vp.isObject()) {
   1639      // "Fast" stringify of primitives would create a wrapper object and thus
   1640      // be slower than regular stringify.
   1641      whySlow = BailReason::PRIMITIVE;
   1642    }
   1643    if (whySlow == BailReason::NO_REASON) {
   1644      if (!FastSerializeJSONProperty(cx, vp, &scx, &whySlow)) {
   1645        return false;
   1646      }
   1647      if (whySlow == BailReason::NO_REASON) {
   1648        // Fast stringify succeeded!
   1649        if (stringifyBehavior != StringifyBehavior::Compare) {
   1650          return true;
   1651        }
   1652        fastJSON = scx.sb.finishAtom();
   1653        if (!fastJSON) {
   1654          return false;
   1655        }
   1656      }
   1657      scx.sb.clear();  // Preserves allocated space.
   1658    }
   1659  }
   1660 
   1661  if (MOZ_UNLIKELY((stringifyBehavior == StringifyBehavior::FastOnly) &&
   1662                   (whySlow != BailReason::NO_REASON))) {
   1663    JS_ReportErrorASCII(cx, "JSON stringify failed mandatory fast path: %s",
   1664                        DescribeStringifyBailReason(whySlow));
   1665    return false;
   1666  }
   1667 
   1668  // Slow, general path.
   1669 
   1670  StringifyContext scx(cx, sb, gap, replacer, propertyList,
   1671                       stringifyBehavior == StringifyBehavior::RestrictedSafe);
   1672  if (!PreprocessValue(cx, wrapper, HandleId(emptyId), vp, &scx)) {
   1673    return false;
   1674  }
   1675  if (IsFilteredValue(vp)) {
   1676    return true;
   1677  }
   1678 
   1679  if (!SerializeJSONProperty(cx, vp, &scx)) {
   1680    return false;
   1681  }
   1682 
   1683  // For StringBehavior::Compare, when the fast path succeeded.
   1684  if (MOZ_UNLIKELY(fastJSON)) {
   1685    JSAtom* slowJSON = scx.sb.finishAtom();
   1686    if (!slowJSON) {
   1687      return false;
   1688    }
   1689    if (fastJSON != slowJSON) {
   1690      MOZ_CRASH("JSON.stringify mismatch between fast and slow paths");
   1691    }
   1692    // Put the JSON back into the StringBuilder for returning.
   1693    if (!sb.append(slowJSON)) {
   1694      return false;
   1695    }
   1696  }
   1697 
   1698  return true;
   1699 }
   1700 
   1701 /* https://262.ecma-international.org/14.0/#sec-internalizejsonproperty */
   1702 static bool InternalizeJSONProperty(JSContext* cx, HandleObject holder,
   1703                                    HandleId name, HandleValue reviver,
   1704                                    Handle<ParseRecordObject*> parseRecord,
   1705                                    MutableHandleValue vp) {
   1706  AutoCheckRecursionLimit recursion(cx);
   1707  if (!recursion.check(cx)) {
   1708    return false;
   1709  }
   1710 
   1711  /* Step 1. */
   1712  RootedValue val(cx);
   1713  if (!GetProperty(cx, holder, holder, name, &val)) {
   1714    return false;
   1715  }
   1716 
   1717  RootedObject context(cx);
   1718  Rooted<ParseRecordObject*> entries(cx);
   1719  // https://tc39.es/proposal-json-parse-with-source/#sec-internalizejsonproperty
   1720  if (parseRecord) {
   1721    bool sameVal = false;
   1722    if (!SameValue(cx, parseRecord->getValue(), val, &sameVal)) {
   1723      return false;
   1724    }
   1725    if (parseRecord->hasValue() && sameVal) {
   1726      if (parseRecord->getParseNode()) {
   1727        MOZ_ASSERT(!val.isObject());
   1728        Rooted<IdValueVector> props(cx, cx);
   1729        if (!props.emplaceBack(
   1730                IdValuePair(NameToId(cx->names().source),
   1731                            StringValue(parseRecord->getParseNode())))) {
   1732          return false;
   1733        }
   1734        context = NewPlainObjectWithUniqueNames(cx, props);
   1735        if (!context) {
   1736          return false;
   1737        }
   1738      }
   1739      entries.set(parseRecord);
   1740    }
   1741  }
   1742  if (!context) {
   1743    context = NewPlainObject(cx);
   1744    if (!context) {
   1745      return false;
   1746    }
   1747  }
   1748 
   1749  /* Step 2. */
   1750  if (val.isObject()) {
   1751    RootedObject obj(cx, &val.toObject());
   1752 
   1753    bool isArray;
   1754    if (!IsArray(cx, obj, &isArray)) {
   1755      return false;
   1756    }
   1757 
   1758    if (isArray) {
   1759      /* Step 2b(i). */
   1760      uint32_t length;
   1761      if (!GetLengthPropertyForArrayLike(cx, obj, &length)) {
   1762        return false;
   1763      }
   1764 
   1765      /* Steps 2b(ii-iii). */
   1766      RootedId id(cx);
   1767      RootedValue newElement(cx);
   1768      for (uint32_t i = 0; i < length; i++) {
   1769        if (!CheckForInterrupt(cx)) {
   1770          return false;
   1771        }
   1772 
   1773        if (!IndexToId(cx, i, &id)) {
   1774          return false;
   1775        }
   1776 
   1777        /* Step 2a(iii)(1). */
   1778        Rooted<ParseRecordObject*> elementRecord(cx);
   1779        if (entries) {
   1780          Rooted<Value> value(cx);
   1781          if (!JS_GetPropertyById(cx, entries, id, &value)) {
   1782            return false;
   1783          }
   1784          if (!value.isNullOrUndefined()) {
   1785            elementRecord = &value.toObject().as<ParseRecordObject>();
   1786          }
   1787        }
   1788        if (!InternalizeJSONProperty(cx, obj, id, reviver, elementRecord,
   1789                                     &newElement)) {
   1790          return false;
   1791        }
   1792 
   1793        ObjectOpResult ignored;
   1794        if (newElement.isUndefined()) {
   1795          /* Step 2b(iii)(3). The spec deliberately ignores strict failure. */
   1796          if (!DeleteProperty(cx, obj, id, ignored)) {
   1797            return false;
   1798          }
   1799        } else {
   1800          /* Step 2b(iii)(4). The spec deliberately ignores strict failure. */
   1801          Rooted<PropertyDescriptor> desc(
   1802              cx, PropertyDescriptor::Data(newElement,
   1803                                           {JS::PropertyAttribute::Configurable,
   1804                                            JS::PropertyAttribute::Enumerable,
   1805                                            JS::PropertyAttribute::Writable}));
   1806          if (!DefineProperty(cx, obj, id, desc, ignored)) {
   1807            return false;
   1808          }
   1809        }
   1810      }
   1811    } else {
   1812      /* Step 2c(i). */
   1813      RootedIdVector keys(cx);
   1814      if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY, &keys)) {
   1815        return false;
   1816      }
   1817 
   1818      /* Step 2c(ii). */
   1819      RootedId id(cx);
   1820      RootedValue newElement(cx);
   1821      for (size_t i = 0, len = keys.length(); i < len; i++) {
   1822        if (!CheckForInterrupt(cx)) {
   1823          return false;
   1824        }
   1825 
   1826        /* Step 2c(ii)(1). */
   1827        id = keys[i];
   1828        Rooted<ParseRecordObject*> entryRecord(cx);
   1829        if (entries) {
   1830          Rooted<Value> value(cx);
   1831          if (!JS_GetPropertyById(cx, entries, id, &value)) {
   1832            return false;
   1833          }
   1834          if (!value.isNullOrUndefined()) {
   1835            entryRecord = &value.toObject().as<ParseRecordObject>();
   1836          }
   1837        }
   1838        if (!InternalizeJSONProperty(cx, obj, id, reviver, entryRecord,
   1839                                     &newElement)) {
   1840          return false;
   1841        }
   1842 
   1843        ObjectOpResult ignored;
   1844        if (newElement.isUndefined()) {
   1845          /* Step 2c(ii)(2). The spec deliberately ignores strict failure. */
   1846          if (!DeleteProperty(cx, obj, id, ignored)) {
   1847            return false;
   1848          }
   1849        } else {
   1850          /* Step 2c(ii)(3). The spec deliberately ignores strict failure. */
   1851          Rooted<PropertyDescriptor> desc(
   1852              cx, PropertyDescriptor::Data(newElement,
   1853                                           {JS::PropertyAttribute::Configurable,
   1854                                            JS::PropertyAttribute::Enumerable,
   1855                                            JS::PropertyAttribute::Writable}));
   1856          if (!DefineProperty(cx, obj, id, desc, ignored)) {
   1857            return false;
   1858          }
   1859        }
   1860      }
   1861    }
   1862  }
   1863 
   1864  /* Step 3. */
   1865  RootedString key(cx, IdToString(cx, name));
   1866  if (!key) {
   1867    return false;
   1868  }
   1869 
   1870  RootedValue keyVal(cx, StringValue(key));
   1871  RootedValue contextVal(cx, ObjectValue(*context));
   1872  return js::Call(cx, reviver, holder, keyVal, val, contextVal, vp);
   1873 }
   1874 
   1875 static bool Revive(JSContext* cx, HandleValue reviver,
   1876                   Handle<ParseRecordObject*> pro, MutableHandleValue vp) {
   1877  Rooted<PlainObject*> obj(cx, NewPlainObject(cx));
   1878  if (!obj) {
   1879    return false;
   1880  }
   1881 
   1882  if (!DefineDataProperty(cx, obj, cx->names().empty_, vp)) {
   1883    return false;
   1884  }
   1885 
   1886  MOZ_ASSERT(pro->getValue() == vp.get());
   1887  Rooted<jsid> id(cx, NameToId(cx->names().empty_));
   1888  return InternalizeJSONProperty(cx, obj, id, reviver, pro, vp);
   1889 }
   1890 
   1891 template <typename CharT>
   1892 bool ParseJSON(JSContext* cx, const mozilla::Range<const CharT> chars,
   1893               MutableHandleValue vp) {
   1894  Rooted<JSONParser<CharT>> parser(cx, cx, chars,
   1895                                   JSONParser<CharT>::ParseType::JSONParse);
   1896  return parser.parse(vp);
   1897 }
   1898 
   1899 template <typename CharT>
   1900 bool js::ParseJSONWithReviver(JSContext* cx,
   1901                              const mozilla::Range<const CharT> chars,
   1902                              HandleValue reviver, MutableHandleValue vp) {
   1903  js::AutoGeckoProfilerEntry pseudoFrame(cx, "parse JSON",
   1904                                         JS::ProfilingCategoryPair::JS_Parsing);
   1905  /* https://262.ecma-international.org/14.0/#sec-json.parse steps 2-10. */
   1906  Rooted<ParseRecordObject*> pro(cx);
   1907  if (IsCallable(reviver)) {
   1908    Rooted<JSONReviveParser<CharT>> parser(cx, cx, chars);
   1909    if (!parser.get().parse(vp, &pro)) {
   1910      return false;
   1911    }
   1912  } else if (!ParseJSON(cx, chars, vp)) {
   1913    return false;
   1914  }
   1915 
   1916  /* Steps 11-12. */
   1917  if (IsCallable(reviver)) {
   1918    return Revive(cx, reviver, pro, vp);
   1919  }
   1920  return true;
   1921 }
   1922 
   1923 template bool js::ParseJSONWithReviver(
   1924    JSContext* cx, const mozilla::Range<const Latin1Char> chars,
   1925    HandleValue reviver, MutableHandleValue vp);
   1926 
   1927 template bool js::ParseJSONWithReviver(
   1928    JSContext* cx, const mozilla::Range<const char16_t> chars,
   1929    HandleValue reviver, MutableHandleValue vp);
   1930 
   1931 static bool json_toSource(JSContext* cx, unsigned argc, Value* vp) {
   1932  CallArgs args = CallArgsFromVp(argc, vp);
   1933  args.rval().setString(cx->names().JSON);
   1934  return true;
   1935 }
   1936 
   1937 /* https://262.ecma-international.org/14.0/#sec-json.parse */
   1938 static bool json_parse(JSContext* cx, unsigned argc, Value* vp) {
   1939  AutoJSMethodProfilerEntry pseudoFrame(cx, "JSON", "parse");
   1940  CallArgs args = CallArgsFromVp(argc, vp);
   1941 
   1942  /* Step 1. */
   1943  JSString* str = (args.length() >= 1) ? ToString<CanGC>(cx, args[0])
   1944                                       : cx->names().undefined;
   1945  if (!str) {
   1946    return false;
   1947  }
   1948 
   1949  JSLinearString* linear = str->ensureLinear(cx);
   1950  if (!linear) {
   1951    return false;
   1952  }
   1953 
   1954  AutoStableStringChars linearChars(cx);
   1955  if (!linearChars.init(cx, linear)) {
   1956    return false;
   1957  }
   1958 
   1959  HandleValue reviver = args.get(1);
   1960 
   1961  /* Steps 2-12. */
   1962  return linearChars.isLatin1()
   1963             ? ParseJSONWithReviver(cx, linearChars.latin1Range(), reviver,
   1964                                    args.rval())
   1965             : ParseJSONWithReviver(cx, linearChars.twoByteRange(), reviver,
   1966                                    args.rval());
   1967 }
   1968 
   1969 /* https://tc39.es/proposal-json-parse-with-source/#sec-json.israwjson */
   1970 static bool json_isRawJSON(JSContext* cx, unsigned argc, Value* vp) {
   1971  AutoJSMethodProfilerEntry pseudoFrame(cx, "JSON", "isRawJSON");
   1972  CallArgs args = CallArgsFromVp(argc, vp);
   1973 
   1974  /* Step 1. */
   1975  if (args.get(0).isObject()) {
   1976    Rooted<JSObject*> obj(cx, &args[0].toObject());
   1977 #ifdef DEBUG
   1978    if (obj->is<RawJSONObject>()) {
   1979      bool objIsFrozen = false;
   1980      MOZ_ASSERT(js::TestIntegrityLevel(cx, obj, IntegrityLevel::Frozen,
   1981                                        &objIsFrozen));
   1982      MOZ_ASSERT(objIsFrozen);
   1983    }
   1984 #endif  // DEBUG
   1985    args.rval().setBoolean(obj->is<RawJSONObject>() ||
   1986                           obj->canUnwrapAs<RawJSONObject>());
   1987    return true;
   1988  }
   1989 
   1990  /* Step 2. */
   1991  args.rval().setBoolean(false);
   1992  return true;
   1993 }
   1994 
   1995 static inline bool IsJSONWhitespace(char16_t ch) {
   1996  return ch == '\t' || ch == '\n' || ch == '\r' || ch == ' ';
   1997 }
   1998 
   1999 /* https://tc39.es/proposal-json-parse-with-source/#sec-json.rawjson */
   2000 static bool json_rawJSON(JSContext* cx, unsigned argc, Value* vp) {
   2001  AutoJSMethodProfilerEntry pseudoFrame(cx, "JSON", "rawJSON");
   2002  CallArgs args = CallArgsFromVp(argc, vp);
   2003 
   2004  /* Step 1. */
   2005  JSString* jsonString = ToString<CanGC>(cx, args.get(0));
   2006  if (!jsonString) {
   2007    return false;
   2008  }
   2009 
   2010  Rooted<JSLinearString*> linear(cx, jsonString->ensureLinear(cx));
   2011  if (!linear) {
   2012    return false;
   2013  }
   2014 
   2015  AutoStableStringChars linearChars(cx);
   2016  if (!linearChars.init(cx, linear)) {
   2017    return false;
   2018  }
   2019 
   2020  /* Step 2. */
   2021  if (linear->empty()) {
   2022    JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
   2023                              JSMSG_JSON_RAW_EMPTY);
   2024    return false;
   2025  }
   2026  if (IsJSONWhitespace(linear->latin1OrTwoByteChar(0)) ||
   2027      IsJSONWhitespace(linear->latin1OrTwoByteChar(linear->length() - 1))) {
   2028    JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
   2029                              JSMSG_JSON_RAW_WHITESPACE);
   2030    return false;
   2031  }
   2032 
   2033  /* Step 3. */
   2034  RootedValue parsedValue(cx);
   2035  if (linearChars.isLatin1()) {
   2036    if (!ParseJSON(cx, linearChars.latin1Range(), &parsedValue)) {
   2037      return false;
   2038    }
   2039  } else {
   2040    if (!ParseJSON(cx, linearChars.twoByteRange(), &parsedValue)) {
   2041      return false;
   2042    }
   2043  }
   2044 
   2045  if (parsedValue.isObject()) {
   2046    JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
   2047                              JSMSG_JSON_RAW_ARRAY_OR_OBJECT);
   2048    return false;
   2049  }
   2050 
   2051  /* Steps 4-6. */
   2052  Rooted<RawJSONObject*> obj(cx, RawJSONObject::create(cx, linear));
   2053  if (!obj) {
   2054    return false;
   2055  }
   2056 
   2057  /* Step 7. */
   2058  if (!js::FreezeObject(cx, obj)) {
   2059    return false;
   2060  }
   2061 
   2062  args.rval().setObject(*obj);
   2063  return true;
   2064 }
   2065 
   2066 /* https://262.ecma-international.org/14.0/#sec-json.stringify */
   2067 bool json_stringify(JSContext* cx, unsigned argc, Value* vp) {
   2068  AutoJSMethodProfilerEntry pseudoFrame(cx, "JSON", "stringify");
   2069  CallArgs args = CallArgsFromVp(argc, vp);
   2070 
   2071  RootedObject replacer(cx,
   2072                        args.get(1).isObject() ? &args[1].toObject() : nullptr);
   2073  RootedValue value(cx, args.get(0));
   2074  RootedValue space(cx, args.get(2));
   2075 
   2076 #ifdef DEBUG
   2077  StringifyBehavior behavior = StringifyBehavior::Compare;
   2078 #else
   2079  StringifyBehavior behavior = StringifyBehavior::Normal;
   2080 #endif
   2081 
   2082  JSStringBuilder sb(cx);
   2083  if (!Stringify(cx, &value, replacer, space, sb, behavior)) {
   2084    return false;
   2085  }
   2086 
   2087  // XXX This can never happen to nsJSON.cpp, but the JSON object
   2088  // needs to support returning undefined. So this is a little awkward
   2089  // for the API, because we want to support streaming writers.
   2090  if (!sb.empty()) {
   2091    JSString* str = sb.finishString();
   2092    if (!str) {
   2093      return false;
   2094    }
   2095    args.rval().setString(str);
   2096  } else {
   2097    args.rval().setUndefined();
   2098  }
   2099 
   2100  return true;
   2101 }
   2102 
   2103 static const JSFunctionSpec json_static_methods[] = {
   2104    JS_FN("toSource", json_toSource, 0, 0),
   2105    JS_FN("parse", json_parse, 2, 0),
   2106    JS_FN("stringify", json_stringify, 3, 0),
   2107    JS_FN("isRawJSON", json_isRawJSON, 1, 0),
   2108    JS_FN("rawJSON", json_rawJSON, 1, 0),
   2109    JS_FS_END,
   2110 };
   2111 
   2112 static const JSPropertySpec json_static_properties[] = {
   2113    JS_STRING_SYM_PS(toStringTag, "JSON", JSPROP_READONLY),
   2114    JS_PS_END,
   2115 };
   2116 
   2117 static JSObject* CreateJSONObject(JSContext* cx, JSProtoKey key) {
   2118  RootedObject proto(cx, &cx->global()->getObjectPrototype());
   2119  return NewTenuredObjectWithGivenProto(cx, &JSONClass, proto);
   2120 }
   2121 
   2122 static const ClassSpec JSONClassSpec = {
   2123    CreateJSONObject,
   2124    nullptr,
   2125    json_static_methods,
   2126    json_static_properties,
   2127 };
   2128 
   2129 const JSClass js::JSONClass = {
   2130    "JSON",
   2131    JSCLASS_HAS_CACHED_PROTO(JSProto_JSON),
   2132    JS_NULL_CLASS_OPS,
   2133    &JSONClassSpec,
   2134 };