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 };