| // Copyright 2016 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/json/json-stringifier.h" |
| |
| #include "src/base/strings.h" |
| #include "src/common/assert-scope.h" |
| #include "src/common/message-template.h" |
| #include "src/execution/protectors-inl.h" |
| #include "src/numbers/conversions.h" |
| #include "src/objects/elements-kind.h" |
| #include "src/objects/heap-number-inl.h" |
| #include "src/objects/js-array-inl.h" |
| #include "src/objects/js-raw-json-inl.h" |
| #include "src/objects/lookup.h" |
| #include "src/objects/objects-inl.h" |
| #include "src/objects/oddball-inl.h" |
| #include "src/objects/ordered-hash-table.h" |
| #include "src/objects/smi.h" |
| #include "src/objects/tagged.h" |
| #include "src/strings/string-builder-inl.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| class JsonStringifier { |
| public: |
| explicit JsonStringifier(Isolate* isolate); |
| |
| ~JsonStringifier() { |
| if (one_byte_ptr_ != one_byte_array_) delete[] one_byte_ptr_; |
| if (two_byte_ptr_) delete[] two_byte_ptr_; |
| DeleteArray(gap_); |
| } |
| |
| V8_WARN_UNUSED_RESULT MaybeHandle<Object> Stringify(Handle<Object> object, |
| Handle<Object> replacer, |
| Handle<Object> gap); |
| |
| private: |
| enum Result { UNCHANGED, SUCCESS, EXCEPTION, NEED_STACK }; |
| |
| bool InitializeReplacer(Handle<Object> replacer); |
| bool InitializeGap(Handle<Object> gap); |
| |
| V8_WARN_UNUSED_RESULT MaybeHandle<Object> ApplyToJsonFunction( |
| Handle<Object> object, Handle<Object> key); |
| V8_WARN_UNUSED_RESULT MaybeHandle<Object> ApplyReplacerFunction( |
| Handle<Object> value, Handle<Object> key, Handle<Object> initial_holder); |
| |
| // Entry point to serialize the object. |
| V8_INLINE Result SerializeObject(Handle<Object> obj) { |
| return Serialize_<false>(obj, false, factory()->empty_string()); |
| } |
| |
| // Serialize an array element. |
| // The index may serve as argument for the toJSON function. |
| V8_INLINE Result SerializeElement(Isolate* isolate, Handle<Object> object, |
| int i) { |
| return Serialize_<false>(object, false, |
| Handle<Object>(Smi::FromInt(i), isolate)); |
| } |
| |
| // Serialize a object property. |
| // The key may or may not be serialized depending on the property. |
| // The key may also serve as argument for the toJSON function. |
| V8_INLINE Result SerializeProperty(Handle<Object> object, bool deferred_comma, |
| Handle<String> deferred_key) { |
| DCHECK(!deferred_key.is_null()); |
| return Serialize_<true>(object, deferred_comma, deferred_key); |
| } |
| |
| template <typename SrcChar, typename DestChar> |
| V8_INLINE void Append(SrcChar c) { |
| DCHECK_EQ(encoding_ == String::ONE_BYTE_ENCODING, sizeof(DestChar) == 1); |
| if (sizeof(DestChar) == 1) { |
| DCHECK_EQ(String::ONE_BYTE_ENCODING, encoding_); |
| one_byte_ptr_[current_index_++] = c; |
| } else { |
| DCHECK_EQ(String::TWO_BYTE_ENCODING, encoding_); |
| // Make sure to use unsigned extension even when SrcChar == char. |
| two_byte_ptr_[current_index_++] = |
| static_cast<std::make_unsigned_t<SrcChar>>(c); |
| } |
| if V8_UNLIKELY (current_index_ == part_length_) Extend(); |
| } |
| |
| V8_INLINE void AppendCharacter(uint8_t c) { |
| if (encoding_ == String::ONE_BYTE_ENCODING) { |
| Append<uint8_t, uint8_t>(c); |
| } else { |
| Append<uint8_t, base::uc16>(c); |
| } |
| } |
| |
| template <int N> |
| V8_INLINE void AppendCStringLiteral(const char (&literal)[N]) { |
| // Note that the literal contains the zero char. |
| const int length = N - 1; |
| static_assert(length > 0); |
| if (length == 1) return AppendCharacter(literal[0]); |
| if (encoding_ == String::ONE_BYTE_ENCODING && CurrentPartCanFit(N)) { |
| const uint8_t* chars = reinterpret_cast<const uint8_t*>(literal); |
| CopyChars<uint8_t, uint8_t>(one_byte_ptr_ + current_index_, chars, |
| length); |
| current_index_ += length; |
| if (current_index_ == part_length_) Extend(); |
| DCHECK(HasValidCurrentIndex()); |
| return; |
| } |
| return AppendCString(literal); |
| } |
| |
| template <typename SrcChar> |
| V8_INLINE void AppendCString(const SrcChar* s) { |
| if (encoding_ == String::ONE_BYTE_ENCODING) { |
| while (*s != '\0') Append<SrcChar, uint8_t>(*s++); |
| } else { |
| while (*s != '\0') Append<SrcChar, base::uc16>(*s++); |
| } |
| } |
| |
| V8_INLINE bool CurrentPartCanFit(int length) { |
| return part_length_ - current_index_ > length; |
| } |
| |
| // We make a rough estimate to find out if the current string can be |
| // serialized without allocating a new string part. The worst case length of |
| // an escaped character is 6. Shifting the remaining string length right by 3 |
| // is a more pessimistic estimate, but faster to calculate. |
| V8_INLINE bool EscapedLengthIfCurrentPartFits(int length) { |
| if (length > kMaxPartLength) return false; |
| static_assert((kMaxPartLength << 3) <= String::kMaxLength); |
| // This shift will not overflow because length is already less than the |
| // maximum part length. |
| return CurrentPartCanFit(length << 3); |
| } |
| |
| void AppendStringByCopy(Tagged<String> string, |
| const DisallowGarbageCollection& no_gc) { |
| DCHECK(encoding_ == String::TWO_BYTE_ENCODING || |
| (string->IsFlat() && |
| String::IsOneByteRepresentationUnderneath(string))); |
| DCHECK(CurrentPartCanFit(string->length())); |
| if (encoding_ == String::ONE_BYTE_ENCODING) { |
| if (String::IsOneByteRepresentationUnderneath(string)) { |
| CopyChars<uint8_t, uint8_t>( |
| one_byte_ptr_ + current_index_, |
| string->GetCharVector<uint8_t>(no_gc).begin(), string->length()); |
| } else { |
| ChangeEncoding(); |
| CopyChars<uint16_t, uint16_t>( |
| two_byte_ptr_ + current_index_, |
| string->GetCharVector<uint16_t>(no_gc).begin(), string->length()); |
| } |
| } else { |
| if (String::IsOneByteRepresentationUnderneath(string)) { |
| CopyChars<uint8_t, uint16_t>( |
| two_byte_ptr_ + current_index_, |
| string->GetCharVector<uint8_t>(no_gc).begin(), string->length()); |
| } else { |
| CopyChars<uint16_t, uint16_t>( |
| two_byte_ptr_ + current_index_, |
| string->GetCharVector<uint16_t>(no_gc).begin(), string->length()); |
| } |
| } |
| current_index_ += string->length(); |
| DCHECK(current_index_ <= part_length_); |
| if (current_index_ == part_length_) Extend(); |
| } |
| |
| V8_NOINLINE void AppendString(Handle<String> string_handle) { |
| { |
| DisallowGarbageCollection no_gc; |
| Tagged<String> string = *string_handle; |
| const bool representation_ok = |
| encoding_ == String::TWO_BYTE_ENCODING || |
| (string->IsFlat() && |
| String::IsOneByteRepresentationUnderneath(string)); |
| if (representation_ok) { |
| while (!CurrentPartCanFit(string->length())) Extend(); |
| AppendStringByCopy(string, no_gc); |
| return; |
| } |
| } |
| SerializeString<true>(string_handle); |
| } |
| |
| bool HasValidCurrentIndex() const { return current_index_ < part_length_; } |
| |
| template <bool deferred_string_key> |
| Result Serialize_(Handle<Object> object, bool comma, Handle<Object> key); |
| |
| V8_INLINE void SerializeDeferredKey(bool deferred_comma, |
| Handle<Object> deferred_key); |
| |
| Result SerializeSmi(Tagged<Smi> object); |
| |
| Result SerializeDouble(double number); |
| V8_INLINE Result SerializeHeapNumber(DirectHandle<HeapNumber> object) { |
| return SerializeDouble(object->value()); |
| } |
| |
| Result SerializeJSPrimitiveWrapper(Handle<JSPrimitiveWrapper> object, |
| Handle<Object> key); |
| |
| V8_INLINE Result SerializeJSArray(Handle<JSArray> object, Handle<Object> key); |
| V8_INLINE Result SerializeJSObject(Handle<JSObject> object, |
| Handle<Object> key); |
| |
| Result SerializeJSProxy(Handle<JSProxy> object, Handle<Object> key); |
| Result SerializeJSReceiverSlow(Handle<JSReceiver> object); |
| template <ElementsKind kind> |
| V8_INLINE Result SerializeFixedArrayWithInterruptCheck( |
| Handle<JSArray> array, uint32_t length, uint32_t* slow_path_index); |
| template <ElementsKind kind> |
| V8_INLINE Result SerializeFixedArrayWithPossibleTransitions( |
| DirectHandle<JSArray> array, uint32_t length, uint32_t* slow_path_index); |
| template <ElementsKind kind, typename T> |
| V8_INLINE Result SerializeFixedArrayElement(Tagged<T> elements, uint32_t i, |
| Tagged<JSArray> array, |
| bool can_treat_hole_as_undefined); |
| Result SerializeArrayLikeSlow(Handle<JSReceiver> object, uint32_t start, |
| uint32_t length); |
| |
| // Returns whether any escape sequences were used. |
| template <bool raw_json> |
| bool SerializeString(Handle<String> object); |
| |
| template <typename DestChar> |
| class NoExtendBuilder { |
| public: |
| NoExtendBuilder(DestChar* start, int* current_index) |
| : current_index_(current_index), start_(start), cursor_(start) {} |
| ~NoExtendBuilder() { |
| *current_index_ += static_cast<int>(cursor_ - start_); |
| } |
| |
| V8_INLINE void Append(DestChar c) { *(cursor_++) = c; } |
| V8_INLINE void AppendCString(const char* s) { |
| const uint8_t* u = reinterpret_cast<const uint8_t*>(s); |
| while (*u != '\0') Append(*(u++)); |
| } |
| |
| // Appends all of the chars from the provided span, but only increases the |
| // cursor by `length`. This allows oversizing the span to the nearest |
| // convenient multiple, allowing CopyChars to run slightly faster. |
| V8_INLINE void AppendChars(base::Vector<const uint8_t> chars, |
| size_t length) { |
| DCHECK_GE(chars.size(), length); |
| CopyChars(cursor_, chars.begin(), chars.size()); |
| cursor_ += length; |
| } |
| |
| private: |
| int* current_index_; |
| DestChar* start_; |
| DestChar* cursor_; |
| }; |
| |
| // A cache of recently seen property keys which were simple. Simple means: |
| // |
| // - Internalized, sequential, one-byte string |
| // - Contains no characters which need escaping |
| // |
| // This can be helpful because it's common for JSON to have lists of similar |
| // objects. Since property keys are internalized, we will see identical key |
| // pointers again and again, and we can use a fast path to copy those keys to |
| // the output. However, strings can be externalized any time JS runs, so the |
| // caller is responsible for checking whether a string is still the expected |
| // type. This cache is cleared on GC, since the GC could move those strings. |
| // Using Handles for the cache has been tried, but is too expensive to set up |
| // when JSON.stringify is called for tiny inputs. |
| class SimplePropertyKeyCache { |
| public: |
| explicit SimplePropertyKeyCache(Isolate* isolate) : isolate_(isolate) { |
| Clear(); |
| isolate->main_thread_local_heap()->AddGCEpilogueCallback( |
| UpdatePointersCallback, this); |
| } |
| |
| ~SimplePropertyKeyCache() { |
| isolate_->main_thread_local_heap()->RemoveGCEpilogueCallback( |
| UpdatePointersCallback, this); |
| } |
| |
| void TryInsert(Tagged<String> string) { |
| ReadOnlyRoots roots(isolate_); |
| if (string->map() == roots.internalized_one_byte_string_map()) { |
| keys_[GetIndex(string)] = MaybeCompress(string); |
| } |
| } |
| |
| bool Contains(Tagged<String> string) { |
| return keys_[GetIndex(string)] == MaybeCompress(string); |
| } |
| |
| private: |
| size_t GetIndex(Tagged<String> string) { |
| // Short strings are 16 bytes long in pointer-compression builds, so the |
| // lower four bits of the pointer may not provide much entropy. |
| return (string.ptr() >> 4) & kIndexMask; |
| } |
| |
| Tagged_t MaybeCompress(Tagged<String> string) { |
| return COMPRESS_POINTERS_BOOL |
| ? V8HeapCompressionScheme::CompressObject(string.ptr()) |
| : static_cast<Tagged_t>(string.ptr()); |
| } |
| |
| void Clear() { MemsetTagged(keys_, Smi::zero(), kSize); } |
| |
| static void UpdatePointersCallback(void* cache) { |
| reinterpret_cast<SimplePropertyKeyCache*>(cache)->Clear(); |
| } |
| |
| static constexpr size_t kSizeBits = 6; |
| static constexpr size_t kSize = 1 << kSizeBits; |
| static constexpr size_t kIndexMask = kSize - 1; |
| |
| Isolate* isolate_; |
| Tagged_t keys_[kSize]; |
| }; |
| |
| // Returns whether any escape sequences were used. |
| template <typename SrcChar, typename DestChar, bool raw_json> |
| V8_INLINE static bool SerializeStringUnchecked_( |
| base::Vector<const SrcChar> src, NoExtendBuilder<DestChar>* dest); |
| |
| // Returns whether any escape sequences were used. |
| template <typename SrcChar, typename DestChar, bool raw_json> |
| V8_INLINE bool SerializeString_(Tagged<String> string, |
| const DisallowGarbageCollection& no_gc); |
| |
| // Tries to do fast-path serialization for a property key, and returns whether |
| // it was successful. |
| template <typename DestChar> |
| bool TrySerializeSimplePropertyKey(Tagged<String> string, |
| const DisallowGarbageCollection& no_gc); |
| |
| template <typename Char> |
| V8_INLINE static bool DoNotEscape(Char c); |
| |
| V8_INLINE void NewLine(); |
| V8_NOINLINE void NewLineOutline(); |
| V8_INLINE void Indent() { indent_++; } |
| V8_INLINE void Unindent() { indent_--; } |
| V8_INLINE void Separator(bool first); |
| |
| Handle<JSReceiver> CurrentHolder(DirectHandle<Object> value, |
| Handle<Object> inital_holder); |
| |
| Result StackPush(Handle<Object> object, Handle<Object> key); |
| void StackPop(); |
| |
| // Uses the current stack_ to provide a detailed error message of |
| // the objects involved in the circular structure. |
| Handle<String> ConstructCircularStructureErrorMessage( |
| DirectHandle<Object> last_key, size_t start_index); |
| // The prefix and postfix count do NOT include the starting and |
| // closing lines of the error message. |
| static const int kCircularErrorMessagePrefixCount = 2; |
| static const int kCircularErrorMessagePostfixCount = 1; |
| |
| static const int kInitialPartLength = 2048; |
| static const int kMaxPartLength = 16 * 1024; |
| static const int kPartLengthGrowthFactor = 2; |
| |
| Factory* factory() { return isolate_->factory(); } |
| |
| V8_NOINLINE void Extend(); |
| V8_NOINLINE void ChangeEncoding(); |
| |
| Isolate* isolate_; |
| String::Encoding encoding_; |
| Handle<FixedArray> property_list_; |
| Handle<JSReceiver> replacer_function_; |
| uint8_t* one_byte_ptr_; |
| base::uc16* gap_; |
| base::uc16* two_byte_ptr_; |
| void* part_ptr_; |
| int indent_; |
| int part_length_; |
| int current_index_; |
| int stack_nesting_level_; |
| bool overflowed_; |
| bool need_stack_; |
| |
| using KeyObject = std::pair<Handle<Object>, Handle<Object>>; |
| std::vector<KeyObject> stack_; |
| |
| SimplePropertyKeyCache key_cache_; |
| uint8_t one_byte_array_[kInitialPartLength]; |
| |
| static const int kJsonEscapeTableEntrySize = 8; |
| static const char* const JsonEscapeTable; |
| static const bool JsonDoNotEscapeFlagTable[]; |
| }; |
| |
| MaybeHandle<Object> JsonStringify(Isolate* isolate, Handle<Object> object, |
| Handle<Object> replacer, Handle<Object> gap) { |
| JsonStringifier stringifier(isolate); |
| return stringifier.Stringify(object, replacer, gap); |
| } |
| |
| // Translation table to escape Latin1 characters. |
| // Table entries start at a multiple of 8 and are null-terminated. |
| const char* const JsonStringifier::JsonEscapeTable = |
| "\\u0000\0 \\u0001\0 \\u0002\0 \\u0003\0 " |
| "\\u0004\0 \\u0005\0 \\u0006\0 \\u0007\0 " |
| "\\b\0 \\t\0 \\n\0 \\u000b\0 " |
| "\\f\0 \\r\0 \\u000e\0 \\u000f\0 " |
| "\\u0010\0 \\u0011\0 \\u0012\0 \\u0013\0 " |
| "\\u0014\0 \\u0015\0 \\u0016\0 \\u0017\0 " |
| "\\u0018\0 \\u0019\0 \\u001a\0 \\u001b\0 " |
| "\\u001c\0 \\u001d\0 \\u001e\0 \\u001f\0 " |
| " \0 !\0 \\\"\0 #\0 " |
| "$\0 %\0 &\0 '\0 " |
| "(\0 )\0 *\0 +\0 " |
| ",\0 -\0 .\0 /\0 " |
| "0\0 1\0 2\0 3\0 " |
| "4\0 5\0 6\0 7\0 " |
| "8\0 9\0 :\0 ;\0 " |
| "<\0 =\0 >\0 ?\0 " |
| "@\0 A\0 B\0 C\0 " |
| "D\0 E\0 F\0 G\0 " |
| "H\0 I\0 J\0 K\0 " |
| "L\0 M\0 N\0 O\0 " |
| "P\0 Q\0 R\0 S\0 " |
| "T\0 U\0 V\0 W\0 " |
| "X\0 Y\0 Z\0 [\0 " |
| "\\\\\0 ]\0 ^\0 _\0 "; |
| |
| const bool JsonStringifier::JsonDoNotEscapeFlagTable[] = { |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| }; |
| |
| JsonStringifier::JsonStringifier(Isolate* isolate) |
| : isolate_(isolate), |
| encoding_(String::ONE_BYTE_ENCODING), |
| gap_(nullptr), |
| two_byte_ptr_(nullptr), |
| indent_(0), |
| part_length_(kInitialPartLength), |
| current_index_(0), |
| stack_nesting_level_(0), |
| overflowed_(false), |
| need_stack_(false), |
| stack_(), |
| key_cache_(isolate) { |
| one_byte_ptr_ = one_byte_array_; |
| part_ptr_ = one_byte_ptr_; |
| } |
| |
| MaybeHandle<Object> JsonStringifier::Stringify(Handle<Object> object, |
| Handle<Object> replacer, |
| Handle<Object> gap) { |
| if (!InitializeReplacer(replacer)) { |
| CHECK(isolate_->has_exception()); |
| return MaybeHandle<Object>(); |
| } |
| if (!IsUndefined(*gap, isolate_) && !InitializeGap(gap)) { |
| CHECK(isolate_->has_exception()); |
| return MaybeHandle<Object>(); |
| } |
| Result result = SerializeObject(object); |
| if (result == NEED_STACK) { |
| indent_ = 0; |
| current_index_ = 0; |
| result = SerializeObject(object); |
| } |
| if (result == UNCHANGED) return factory()->undefined_value(); |
| if (result == SUCCESS) { |
| if (overflowed_ || current_index_ > String::kMaxLength) { |
| THROW_NEW_ERROR(isolate_, NewInvalidStringLengthError()); |
| } |
| if (encoding_ == String::ONE_BYTE_ENCODING) { |
| return isolate_->factory() |
| ->NewStringFromOneByte(base::OneByteVector( |
| reinterpret_cast<char*>(one_byte_ptr_), current_index_)) |
| .ToHandleChecked(); |
| } else { |
| return isolate_->factory()->NewStringFromTwoByte( |
| base::Vector<const base::uc16>(two_byte_ptr_, current_index_)); |
| } |
| } |
| DCHECK(result == EXCEPTION); |
| CHECK(isolate_->has_exception()); |
| return MaybeHandle<Object>(); |
| } |
| |
| bool JsonStringifier::InitializeReplacer(Handle<Object> replacer) { |
| DCHECK(property_list_.is_null()); |
| DCHECK(replacer_function_.is_null()); |
| Maybe<bool> is_array = Object::IsArray(replacer); |
| if (is_array.IsNothing()) return false; |
| if (is_array.FromJust()) { |
| HandleScope handle_scope(isolate_); |
| Handle<OrderedHashSet> set = factory()->NewOrderedHashSet(); |
| Handle<Object> length_obj; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate_, length_obj, |
| Object::GetLengthFromArrayLike(isolate_, Cast<JSReceiver>(replacer)), |
| false); |
| uint32_t length; |
| if (!Object::ToUint32(*length_obj, &length)) length = kMaxUInt32; |
| for (uint32_t i = 0; i < length; i++) { |
| Handle<Object> element; |
| Handle<String> key; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate_, element, Object::GetElement(isolate_, replacer, i), false); |
| if (IsNumber(*element) || IsString(*element)) { |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate_, key, Object::ToString(isolate_, element), false); |
| } else if (IsJSPrimitiveWrapper(*element)) { |
| DirectHandle<Object> value(Cast<JSPrimitiveWrapper>(element)->value(), |
| isolate_); |
| if (IsNumber(*value) || IsString(*value)) { |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate_, key, Object::ToString(isolate_, element), false); |
| } |
| } |
| if (key.is_null()) continue; |
| // Object keys are internalized, so do it here. |
| key = factory()->InternalizeString(key); |
| MaybeHandle<OrderedHashSet> set_candidate = |
| OrderedHashSet::Add(isolate_, set, key); |
| if (!set_candidate.ToHandle(&set)) { |
| CHECK(isolate_->has_exception()); |
| return false; |
| } |
| } |
| property_list_ = OrderedHashSet::ConvertToKeysArray( |
| isolate_, set, GetKeysConversion::kKeepNumbers); |
| property_list_ = handle_scope.CloseAndEscape(property_list_); |
| } else if (IsCallable(*replacer)) { |
| replacer_function_ = Cast<JSReceiver>(replacer); |
| } |
| return true; |
| } |
| |
| bool JsonStringifier::InitializeGap(Handle<Object> gap) { |
| DCHECK_NULL(gap_); |
| HandleScope scope(isolate_); |
| if (IsJSPrimitiveWrapper(*gap)) { |
| DirectHandle<Object> value(Cast<JSPrimitiveWrapper>(gap)->value(), |
| isolate_); |
| if (IsString(*value)) { |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate_, gap, |
| Object::ToString(isolate_, gap), false); |
| } else if (IsNumber(*value)) { |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate_, gap, |
| Object::ToNumber(isolate_, gap), false); |
| } |
| } |
| |
| if (IsString(*gap)) { |
| auto gap_string = Cast<String>(gap); |
| if (gap_string->length() > 0) { |
| int gap_length = std::min(gap_string->length(), 10); |
| gap_ = NewArray<base::uc16>(gap_length + 1); |
| String::WriteToFlat(*gap_string, gap_, 0, gap_length); |
| for (int i = 0; i < gap_length; i++) { |
| if (gap_[i] > String::kMaxOneByteCharCode) { |
| ChangeEncoding(); |
| break; |
| } |
| } |
| gap_[gap_length] = '\0'; |
| } |
| } else if (IsNumber(*gap)) { |
| double value = std::min(Object::NumberValue(*gap), 10.0); |
| if (value > 0) { |
| int gap_length = DoubleToInt32(value); |
| gap_ = NewArray<base::uc16>(gap_length + 1); |
| for (int i = 0; i < gap_length; i++) gap_[i] = ' '; |
| gap_[gap_length] = '\0'; |
| } |
| } |
| return true; |
| } |
| |
| MaybeHandle<Object> JsonStringifier::ApplyToJsonFunction(Handle<Object> object, |
| Handle<Object> key) { |
| HandleScope scope(isolate_); |
| |
| // Retrieve toJSON function. The LookupIterator automatically handles |
| // the ToObject() equivalent ("GetRoot") if {object} is a BigInt. |
| Handle<Object> fun; |
| LookupIterator it(isolate_, object, factory()->toJSON_string(), |
| LookupIterator::PROTOTYPE_CHAIN_SKIP_INTERCEPTOR); |
| ASSIGN_RETURN_ON_EXCEPTION(isolate_, fun, Object::GetProperty(&it)); |
| if (!IsCallable(*fun)) return object; |
| |
| // Call toJSON function. |
| if (IsSmi(*key)) key = factory()->NumberToString(key); |
| Handle<Object> argv[] = {key}; |
| ASSIGN_RETURN_ON_EXCEPTION(isolate_, object, |
| Execution::Call(isolate_, fun, object, 1, argv)); |
| return scope.CloseAndEscape(object); |
| } |
| |
| MaybeHandle<Object> JsonStringifier::ApplyReplacerFunction( |
| Handle<Object> value, Handle<Object> key, Handle<Object> initial_holder) { |
| HandleScope scope(isolate_); |
| if (IsSmi(*key)) key = factory()->NumberToString(key); |
| Handle<Object> argv[] = {key, value}; |
| Handle<JSReceiver> holder = CurrentHolder(value, initial_holder); |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate_, value, |
| Execution::Call(isolate_, replacer_function_, holder, 2, argv)); |
| return scope.CloseAndEscape(value); |
| } |
| |
| Handle<JSReceiver> JsonStringifier::CurrentHolder( |
| DirectHandle<Object> value, Handle<Object> initial_holder) { |
| if (stack_.empty()) { |
| Handle<JSObject> holder = |
| factory()->NewJSObject(isolate_->object_function()); |
| JSObject::AddProperty(isolate_, holder, factory()->empty_string(), |
| initial_holder, NONE); |
| return holder; |
| } else { |
| return Handle<JSReceiver>(Cast<JSReceiver>(*stack_.back().second), |
| isolate_); |
| } |
| } |
| |
| JsonStringifier::Result JsonStringifier::StackPush(Handle<Object> object, |
| Handle<Object> key) { |
| if (!need_stack_) { |
| ++stack_nesting_level_; |
| if V8_UNLIKELY (stack_nesting_level_ > 10) { |
| need_stack_ = true; |
| return NEED_STACK; |
| } |
| return SUCCESS; |
| } |
| StackLimitCheck check(isolate_); |
| if (check.HasOverflowed()) { |
| isolate_->StackOverflow(); |
| return EXCEPTION; |
| } |
| |
| { |
| DisallowGarbageCollection no_gc; |
| Tagged<Object> raw_obj = *object; |
| size_t size = stack_.size(); |
| for (size_t i = 0; i < size; ++i) { |
| if (*stack_[i].second == raw_obj) { |
| AllowGarbageCollection allow_to_return_error; |
| Handle<String> circle_description = |
| ConstructCircularStructureErrorMessage(key, i); |
| DirectHandle<Object> error = factory()->NewTypeError( |
| MessageTemplate::kCircularStructure, circle_description); |
| isolate_->Throw(*error); |
| return EXCEPTION; |
| } |
| } |
| } |
| stack_.emplace_back(key, object); |
| return SUCCESS; |
| } |
| |
| void JsonStringifier::StackPop() { |
| if V8_LIKELY (!need_stack_) { |
| --stack_nesting_level_; |
| return; |
| } |
| stack_.pop_back(); |
| } |
| |
| class CircularStructureMessageBuilder { |
| public: |
| explicit CircularStructureMessageBuilder(Isolate* isolate) |
| : builder_(isolate) {} |
| |
| void AppendStartLine(Handle<Object> start_object) { |
| builder_.AppendCString(kStartPrefix); |
| builder_.AppendCStringLiteral("starting at object with constructor "); |
| AppendConstructorName(start_object); |
| } |
| |
| void AppendNormalLine(DirectHandle<Object> key, Handle<Object> object) { |
| builder_.AppendCString(kLinePrefix); |
| AppendKey(key); |
| builder_.AppendCStringLiteral(" -> object with constructor "); |
| AppendConstructorName(object); |
| } |
| |
| void AppendClosingLine(DirectHandle<Object> closing_key) { |
| builder_.AppendCString(kEndPrefix); |
| AppendKey(closing_key); |
| builder_.AppendCStringLiteral(" closes the circle"); |
| } |
| |
| void AppendEllipsis() { |
| builder_.AppendCString(kLinePrefix); |
| builder_.AppendCStringLiteral("..."); |
| } |
| |
| MaybeDirectHandle<String> Finish() { return builder_.Finish(); } |
| |
| private: |
| void AppendConstructorName(Handle<Object> object) { |
| builder_.AppendCharacter('\''); |
| DirectHandle<String> constructor_name = JSReceiver::GetConstructorName( |
| builder_.isolate(), Cast<JSReceiver>(object)); |
| builder_.AppendString(constructor_name); |
| builder_.AppendCharacter('\''); |
| } |
| |
| // A key can either be a string, the empty string or a Smi. |
| void AppendKey(DirectHandle<Object> key) { |
| if (IsSmi(*key)) { |
| builder_.AppendCStringLiteral("index "); |
| AppendSmi(Cast<Smi>(*key)); |
| return; |
| } |
| |
| CHECK(IsString(*key)); |
| DirectHandle<String> key_as_string = Cast<String>(key); |
| if (key_as_string->length() == 0) { |
| builder_.AppendCStringLiteral("<anonymous>"); |
| } else { |
| builder_.AppendCStringLiteral("property '"); |
| builder_.AppendString(key_as_string); |
| builder_.AppendCharacter('\''); |
| } |
| } |
| |
| void AppendSmi(Tagged<Smi> smi) { |
| static const int kBufferSize = 100; |
| char chars[kBufferSize]; |
| base::Vector<char> buffer(chars, kBufferSize); |
| builder_.AppendCString(IntToCString(smi.value(), buffer)); |
| } |
| |
| IncrementalStringBuilder builder_; |
| static constexpr const char* kStartPrefix = "\n --> "; |
| static constexpr const char* kEndPrefix = "\n --- "; |
| static constexpr const char* kLinePrefix = "\n | "; |
| }; |
| |
| Handle<String> JsonStringifier::ConstructCircularStructureErrorMessage( |
| DirectHandle<Object> last_key, size_t start_index) { |
| DCHECK(start_index < stack_.size()); |
| CircularStructureMessageBuilder builder(isolate_); |
| |
| // We track the index to be printed next for better readability. |
| size_t index = start_index; |
| const size_t stack_size = stack_.size(); |
| |
| builder.AppendStartLine(stack_[index++].second); |
| |
| // Append a maximum of kCircularErrorMessagePrefixCount normal lines. |
| const size_t prefix_end = |
| std::min(stack_size, index + kCircularErrorMessagePrefixCount); |
| for (; index < prefix_end; ++index) { |
| builder.AppendNormalLine(stack_[index].first, stack_[index].second); |
| } |
| |
| // If the circle consists of too many objects, we skip them and just |
| // print an ellipsis. |
| if (stack_size > index + kCircularErrorMessagePostfixCount) { |
| builder.AppendEllipsis(); |
| } |
| |
| // Since we calculate the postfix lines from the back of the stack, |
| // we have to ensure that lines are not printed twice. |
| index = std::max(index, stack_size - kCircularErrorMessagePostfixCount); |
| for (; index < stack_size; ++index) { |
| builder.AppendNormalLine(stack_[index].first, stack_[index].second); |
| } |
| |
| builder.AppendClosingLine(last_key); |
| |
| Handle<String> result; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate_, result, |
| indirect_handle(builder.Finish(), isolate_), |
| factory()->empty_string()); |
| return result; |
| } |
| |
| bool MayHaveInterestingProperties(Isolate* isolate, Tagged<JSReceiver> object) { |
| for (PrototypeIterator iter(isolate, object, kStartAtReceiver); |
| !iter.IsAtEnd(); iter.Advance()) { |
| if (iter.GetCurrent()->map()->may_have_interesting_properties()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| template <bool deferred_string_key> |
| JsonStringifier::Result JsonStringifier::Serialize_(Handle<Object> object, |
| bool comma, |
| Handle<Object> key) { |
| StackLimitCheck interrupt_check(isolate_); |
| if (interrupt_check.InterruptRequested() && |
| IsException(isolate_->stack_guard()->HandleInterrupts(), isolate_)) { |
| return EXCEPTION; |
| } |
| |
| Handle<Object> initial_value = object; |
| PtrComprCageBase cage_base(isolate_); |
| if (!IsSmi(*object)) { |
| InstanceType instance_type = |
| Cast<HeapObject>(*object)->map(cage_base)->instance_type(); |
| if ((InstanceTypeChecker::IsJSReceiver(instance_type) && |
| MayHaveInterestingProperties(isolate_, Cast<JSReceiver>(*object))) || |
| InstanceTypeChecker::IsBigInt(instance_type)) { |
| if (!need_stack_ && stack_nesting_level_ > 0) { |
| need_stack_ = true; |
| return NEED_STACK; |
| } |
| need_stack_ = true; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate_, object, ApplyToJsonFunction(object, key), EXCEPTION); |
| } |
| } |
| if (!replacer_function_.is_null()) { |
| need_stack_ = true; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate_, object, ApplyReplacerFunction(object, key, initial_value), |
| EXCEPTION); |
| } |
| |
| if (IsSmi(*object)) { |
| if (deferred_string_key) SerializeDeferredKey(comma, key); |
| return SerializeSmi(Cast<Smi>(*object)); |
| } |
| |
| InstanceType instance_type = |
| Cast<HeapObject>(*object)->map(cage_base)->instance_type(); |
| switch (instance_type) { |
| case HEAP_NUMBER_TYPE: |
| if (deferred_string_key) SerializeDeferredKey(comma, key); |
| return SerializeHeapNumber(Cast<HeapNumber>(object)); |
| case BIGINT_TYPE: |
| isolate_->Throw( |
| *factory()->NewTypeError(MessageTemplate::kBigIntSerializeJSON)); |
| return EXCEPTION; |
| case ODDBALL_TYPE: |
| switch (Cast<Oddball>(*object)->kind()) { |
| case Oddball::kFalse: |
| if (deferred_string_key) SerializeDeferredKey(comma, key); |
| AppendCStringLiteral("false"); |
| return SUCCESS; |
| case Oddball::kTrue: |
| if (deferred_string_key) SerializeDeferredKey(comma, key); |
| AppendCStringLiteral("true"); |
| return SUCCESS; |
| case Oddball::kNull: |
| if (deferred_string_key) SerializeDeferredKey(comma, key); |
| AppendCStringLiteral("null"); |
| return SUCCESS; |
| default: |
| return UNCHANGED; |
| } |
| case JS_ARRAY_TYPE: |
| if (deferred_string_key) SerializeDeferredKey(comma, key); |
| return SerializeJSArray(Cast<JSArray>(object), key); |
| case JS_PRIMITIVE_WRAPPER_TYPE: |
| if (!need_stack_) { |
| need_stack_ = true; |
| return NEED_STACK; |
| } |
| if (deferred_string_key) SerializeDeferredKey(comma, key); |
| return SerializeJSPrimitiveWrapper(Cast<JSPrimitiveWrapper>(object), key); |
| case SYMBOL_TYPE: |
| return UNCHANGED; |
| case JS_RAW_JSON_TYPE: |
| if (deferred_string_key) SerializeDeferredKey(comma, key); |
| { |
| Handle<JSRawJson> raw_json_obj = Cast<JSRawJson>(object); |
| Handle<String> raw_json; |
| if (raw_json_obj->HasInitialLayout(isolate_)) { |
| // Fast path: the object returned by JSON.rawJSON has its initial map |
| // intact. |
| raw_json = Cast<String>(handle( |
| raw_json_obj->InObjectPropertyAt(JSRawJson::kRawJsonInitialIndex), |
| isolate_)); |
| } else { |
| // Slow path: perform a property get for "rawJSON". Because raw JSON |
| // objects are created frozen, it is still guaranteed that there will |
| // be a property named "rawJSON" that is a String. Their initial maps |
| // only change due to VM-internal operations like being optimized for |
| // being used as a prototype. |
| raw_json = Cast<String>( |
| JSObject::GetProperty(isolate_, raw_json_obj, |
| isolate_->factory()->raw_json_string()) |
| .ToHandleChecked()); |
| } |
| AppendString(raw_json); |
| } |
| return SUCCESS; |
| case HOLE_TYPE: |
| UNREACHABLE(); |
| #if V8_ENABLE_WEBASSEMBLY |
| case WASM_STRUCT_TYPE: |
| case WASM_ARRAY_TYPE: |
| return UNCHANGED; |
| #endif |
| default: |
| if (InstanceTypeChecker::IsString(instance_type)) { |
| if (deferred_string_key) SerializeDeferredKey(comma, key); |
| SerializeString<false>(Cast<String>(object)); |
| return SUCCESS; |
| } else { |
| // Make sure that we have a JSReceiver before we cast it to one. |
| // If we ever leak an internal object that is not a JSReceiver it could |
| // end up here and lead to a type confusion. |
| CHECK(IsJSReceiver(*object)); |
| if (IsCallable(Cast<HeapObject>(*object), cage_base)) return UNCHANGED; |
| // Go to slow path for global proxy and objects requiring access checks. |
| if (deferred_string_key) SerializeDeferredKey(comma, key); |
| if (InstanceTypeChecker::IsJSProxy(instance_type)) { |
| return SerializeJSProxy(Cast<JSProxy>(object), key); |
| } |
| // WASM_{STRUCT,ARRAY}_TYPE are handled in `case:` blocks above. |
| DCHECK(IsJSObject(*object)); |
| return SerializeJSObject(Cast<JSObject>(object), key); |
| } |
| } |
| |
| UNREACHABLE(); |
| } |
| |
| JsonStringifier::Result JsonStringifier::SerializeJSPrimitiveWrapper( |
| Handle<JSPrimitiveWrapper> object, Handle<Object> key) { |
| Tagged<Object> raw = object->value(); |
| if (IsString(raw)) { |
| Handle<Object> value; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate_, value, Object::ToString(isolate_, object), EXCEPTION); |
| SerializeString<false>(Cast<String>(value)); |
| } else if (IsNumber(raw)) { |
| Handle<Object> value; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate_, value, Object::ToNumber(isolate_, object), EXCEPTION); |
| if (IsSmi(*value)) return SerializeSmi(Cast<Smi>(*value)); |
| SerializeHeapNumber(Cast<HeapNumber>(value)); |
| } else if (IsBigInt(raw)) { |
| isolate_->Throw( |
| *factory()->NewTypeError(MessageTemplate::kBigIntSerializeJSON)); |
| return EXCEPTION; |
| } else if (IsBoolean(raw)) { |
| if (IsTrue(raw, isolate_)) { |
| AppendCStringLiteral("true"); |
| } else { |
| AppendCStringLiteral("false"); |
| } |
| } else { |
| // ES6 24.3.2.1 step 10.c, serialize as an ordinary JSObject. |
| return SerializeJSObject(object, key); |
| } |
| return SUCCESS; |
| } |
| |
| JsonStringifier::Result JsonStringifier::SerializeSmi(Tagged<Smi> object) { |
| static const int kBufferSize = 100; |
| char chars[kBufferSize]; |
| base::Vector<char> buffer(chars, kBufferSize); |
| AppendCString(IntToCString(object.value(), buffer)); |
| return SUCCESS; |
| } |
| |
| JsonStringifier::Result JsonStringifier::SerializeDouble(double number) { |
| if (std::isinf(number) || std::isnan(number)) { |
| AppendCStringLiteral("null"); |
| return SUCCESS; |
| } |
| static const int kBufferSize = 100; |
| char chars[kBufferSize]; |
| base::Vector<char> buffer(chars, kBufferSize); |
| AppendCString(DoubleToCString(number, buffer)); |
| return SUCCESS; |
| } |
| |
| namespace { |
| |
| bool CanTreatHoleAsUndefined(Isolate* isolate, Tagged<JSArray> object) { |
| // We can treat holes as undefined if the {object}s prototype is either the |
| // initial Object.prototype or the initial Array.prototype, which are both |
| // guarded by the "no elements" protector. |
| if (!Protectors::IsNoElementsIntact(isolate)) return false; |
| Tagged<HeapObject> proto = object->map(isolate)->prototype(); |
| if (!isolate->IsInAnyContext(proto, Context::INITIAL_ARRAY_PROTOTYPE_INDEX) && |
| !isolate->IsInAnyContext(proto, |
| Context::INITIAL_OBJECT_PROTOTYPE_INDEX)) { |
| return false; |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| JsonStringifier::Result JsonStringifier::SerializeJSArray( |
| Handle<JSArray> object, Handle<Object> key) { |
| uint32_t length = 0; |
| CHECK(Object::ToArrayLength(object->length(), &length)); |
| DCHECK(!IsAccessCheckNeeded(*object)); |
| if (length == 0) { |
| AppendCStringLiteral("[]"); |
| return SUCCESS; |
| } |
| |
| Result stack_push = StackPush(object, key); |
| if (stack_push != SUCCESS) return stack_push; |
| |
| AppendCharacter('['); |
| Indent(); |
| uint32_t slow_path_index = 0; |
| Result result = UNCHANGED; |
| if (replacer_function_.is_null()) { |
| #define CASE_WITH_INTERRUPT(kind) \ |
| case kind: \ |
| result = SerializeFixedArrayWithInterruptCheck<kind>(object, length, \ |
| &slow_path_index); \ |
| break; |
| #define CASE_WITH_TRANSITION(kind) \ |
| case kind: \ |
| result = SerializeFixedArrayWithPossibleTransitions<kind>( \ |
| object, length, &slow_path_index); \ |
| break; |
| |
| switch (object->GetElementsKind()) { |
| CASE_WITH_INTERRUPT(PACKED_SMI_ELEMENTS) |
| CASE_WITH_INTERRUPT(HOLEY_SMI_ELEMENTS) |
| CASE_WITH_TRANSITION(PACKED_ELEMENTS) |
| CASE_WITH_TRANSITION(HOLEY_ELEMENTS) |
| CASE_WITH_INTERRUPT(PACKED_DOUBLE_ELEMENTS) |
| CASE_WITH_INTERRUPT(HOLEY_DOUBLE_ELEMENTS) |
| default: |
| break; |
| } |
| |
| #undef CASE_WITH_TRANSITION |
| #undef CASE_WITH_INTERRUPT |
| } |
| if (result == UNCHANGED) { |
| // Slow path for non-fast elements and fall-back in edge cases. |
| result = SerializeArrayLikeSlow(object, slow_path_index, length); |
| } |
| if (result != SUCCESS) return result; |
| Unindent(); |
| NewLine(); |
| AppendCharacter(']'); |
| StackPop(); |
| return SUCCESS; |
| } |
| |
| template <ElementsKind kind> |
| JsonStringifier::Result JsonStringifier::SerializeFixedArrayWithInterruptCheck( |
| Handle<JSArray> array, uint32_t length, uint32_t* slow_path_index) { |
| static_assert(IsSmiElementsKind(kind) || IsDoubleElementsKind(kind)); |
| using ArrayT = typename std::conditional<IsDoubleElementsKind(kind), |
| FixedDoubleArray, FixedArray>::type; |
| |
| StackLimitCheck interrupt_check(isolate_); |
| constexpr uint32_t kInterruptLength = 4000; |
| uint32_t limit = std::min(length, kInterruptLength); |
| constexpr uint32_t kMaxAllowedFastPackedLength = |
| std::numeric_limits<uint32_t>::max() - kInterruptLength; |
| static_assert(FixedArray::kMaxLength < kMaxAllowedFastPackedLength); |
| |
| constexpr bool is_holey = IsHoleyElementsKind(kind); |
| bool bailout_on_hole = |
| is_holey ? !CanTreatHoleAsUndefined(isolate_, *array) : true; |
| |
| uint32_t i = 0; |
| while (true) { |
| for (; i < limit; i++) { |
| Result result = SerializeFixedArrayElement<kind>( |
| Cast<ArrayT>(array->elements()), i, *array, bailout_on_hole); |
| if constexpr (is_holey) { |
| if (result != SUCCESS) { |
| *slow_path_index = i; |
| return result; |
| } |
| } else { |
| USE(result); |
| DCHECK_EQ(result, SUCCESS); |
| } |
| } |
| if (i >= length) return SUCCESS; |
| DCHECK_LT(limit, kMaxAllowedFastPackedLength); |
| limit = std::min(length, limit + kInterruptLength); |
| if (interrupt_check.InterruptRequested() && |
| IsException(isolate_->stack_guard()->HandleInterrupts(), isolate_)) { |
| return EXCEPTION; |
| } |
| } |
| return SUCCESS; |
| } |
| |
| template <ElementsKind kind> |
| JsonStringifier::Result |
| JsonStringifier::SerializeFixedArrayWithPossibleTransitions( |
| DirectHandle<JSArray> array, uint32_t length, uint32_t* slow_path_index) { |
| static_assert(IsObjectElementsKind(kind)); |
| |
| HandleScope handle_scope(isolate_); |
| DirectHandle<Object> old_length(array->length(), isolate_); |
| constexpr bool is_holey = IsHoleyElementsKind(kind); |
| bool should_check_treat_hole_as_undefined = true; |
| for (uint32_t i = 0; i < length; i++) { |
| if (array->length() != *old_length || kind != array->GetElementsKind()) { |
| // Array was modified during SerializeElement. |
| *slow_path_index = i; |
| return UNCHANGED; |
| } |
| Tagged<Object> current_element = |
| Cast<FixedArray>(array->elements())->get(i); |
| if (is_holey && IsTheHole(current_element)) { |
| if (should_check_treat_hole_as_undefined) { |
| if (!CanTreatHoleAsUndefined(isolate_, *array)) { |
| *slow_path_index = i; |
| return UNCHANGED; |
| } |
| should_check_treat_hole_as_undefined = false; |
| } |
| Separator(i == 0); |
| AppendCStringLiteral("null"); |
| } else { |
| Separator(i == 0); |
| Result result = |
| SerializeElement(isolate_, handle(current_element, isolate_), i); |
| if (result == UNCHANGED) { |
| AppendCStringLiteral("null"); |
| } else if (result != SUCCESS) { |
| return result; |
| } |
| if constexpr (is_holey) { |
| should_check_treat_hole_as_undefined = true; |
| } |
| } |
| } |
| return SUCCESS; |
| } |
| |
| template <ElementsKind kind, typename T> |
| JsonStringifier::Result JsonStringifier::SerializeFixedArrayElement( |
| Tagged<T> elements, uint32_t i, Tagged<JSArray> array, |
| bool bailout_on_hole) { |
| if constexpr (IsHoleyElementsKind(kind)) { |
| if (elements->is_the_hole(isolate_, i)) { |
| if (bailout_on_hole) return UNCHANGED; |
| Separator(i == 0); |
| AppendCStringLiteral("null"); |
| return SUCCESS; |
| } |
| } |
| DCHECK(!elements->is_the_hole(isolate_, i)); |
| Separator(i == 0); |
| if constexpr (IsSmiElementsKind(kind)) { |
| SerializeSmi(Cast<Smi>(elements->get(i))); |
| } else if constexpr (IsDoubleElementsKind(kind)) { |
| SerializeDouble(elements->get_scalar(i)); |
| } else { |
| UNREACHABLE(); |
| } |
| return SUCCESS; |
| } |
| |
| JsonStringifier::Result JsonStringifier::SerializeArrayLikeSlow( |
| Handle<JSReceiver> object, uint32_t start, uint32_t length) { |
| if (!need_stack_) { |
| need_stack_ = true; |
| return NEED_STACK; |
| } |
| // We need to write out at least two characters per array element. |
| static const int kMaxSerializableArrayLength = String::kMaxLength / 2; |
| if (length > kMaxSerializableArrayLength) { |
| isolate_->Throw(*isolate_->factory()->NewInvalidStringLengthError()); |
| return EXCEPTION; |
| } |
| HandleScope handle_scope(isolate_); |
| for (uint32_t i = start; i < length; i++) { |
| Separator(i == 0); |
| Handle<Object> element; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate_, element, JSReceiver::GetElement(isolate_, object, i), |
| EXCEPTION); |
| Result result = SerializeElement(isolate_, element, i); |
| if (result == SUCCESS) continue; |
| if (result == UNCHANGED) { |
| // Detect overflow sooner for large sparse arrays. |
| if (overflowed_) { |
| isolate_->Throw(*isolate_->factory()->NewInvalidStringLengthError()); |
| return EXCEPTION; |
| } |
| AppendCStringLiteral("null"); |
| } else { |
| return result; |
| } |
| } |
| return SUCCESS; |
| } |
| |
| namespace { |
| V8_INLINE bool CanFastSerializeJSObject(PtrComprCageBase cage_base, |
| Tagged<JSObject> raw_object, |
| Isolate* isolate) { |
| DisallowGarbageCollection no_gc; |
| if (IsCustomElementsReceiverMap(raw_object->map(cage_base))) return false; |
| if (!raw_object->HasFastProperties(cage_base)) return false; |
| auto roots = ReadOnlyRoots(isolate); |
| auto elements = raw_object->elements(cage_base); |
| return elements == roots.empty_fixed_array() || |
| elements == roots.empty_slow_element_dictionary(); |
| } |
| } // namespace |
| |
| JsonStringifier::Result JsonStringifier::SerializeJSObject( |
| Handle<JSObject> object, Handle<Object> key) { |
| PtrComprCageBase cage_base(isolate_); |
| HandleScope handle_scope(isolate_); |
| |
| if (!property_list_.is_null() || |
| !CanFastSerializeJSObject(cage_base, *object, isolate_)) { |
| if (!need_stack_) { |
| need_stack_ = true; |
| return NEED_STACK; |
| } |
| Result stack_push = StackPush(object, key); |
| if (stack_push != SUCCESS) return stack_push; |
| Result result = SerializeJSReceiverSlow(object); |
| if (result != SUCCESS) return result; |
| StackPop(); |
| return SUCCESS; |
| } |
| |
| DCHECK(!IsJSGlobalProxy(*object)); |
| DCHECK(!object->HasIndexedInterceptor()); |
| DCHECK(!object->HasNamedInterceptor()); |
| |
| DirectHandle<Map> map(object->map(cage_base), isolate_); |
| if (map->NumberOfOwnDescriptors() == 0) { |
| AppendCStringLiteral("{}"); |
| return SUCCESS; |
| } |
| |
| Result stack_push = StackPush(object, key); |
| if (stack_push != SUCCESS) return stack_push; |
| AppendCharacter('{'); |
| Indent(); |
| bool comma = false; |
| for (InternalIndex i : map->IterateOwnDescriptors()) { |
| Handle<String> key_name; |
| PropertyDetails details = PropertyDetails::Empty(); |
| { |
| DisallowGarbageCollection no_gc; |
| Tagged<DescriptorArray> descriptors = |
| map->instance_descriptors(cage_base); |
| Tagged<Name> name = descriptors->GetKey(i); |
| // TODO(rossberg): Should this throw? |
| if (!IsString(name, cage_base)) continue; |
| key_name = handle(Cast<String>(name), isolate_); |
| details = descriptors->GetDetails(i); |
| } |
| if (details.IsDontEnum()) continue; |
| Handle<Object> property; |
| if (details.location() == PropertyLocation::kField && |
| *map == object->map(cage_base)) { |
| DCHECK_EQ(PropertyKind::kData, details.kind()); |
| FieldIndex field_index = FieldIndex::ForDetails(*map, details); |
| if (replacer_function_.is_null()) { |
| // If there's no replacer function, read the raw property to avoid |
| // reboxing doubles in mutable boxes. |
| property = handle(object->RawFastPropertyAt(field_index), isolate_); |
| } else { |
| // Rebox the value if there is a replacer function since it could change |
| // the value in the box. |
| property = JSObject::FastPropertyAt( |
| isolate_, object, details.representation(), field_index); |
| } |
| } else { |
| if (!need_stack_) { |
| need_stack_ = true; |
| return NEED_STACK; |
| } |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate_, property, |
| Object::GetPropertyOrElement(isolate_, object, key_name), EXCEPTION); |
| } |
| Result result = SerializeProperty(property, comma, key_name); |
| if (!comma && result == SUCCESS) comma = true; |
| if (result == EXCEPTION || result == NEED_STACK) return result; |
| } |
| Unindent(); |
| if (comma) NewLine(); |
| AppendCharacter('}'); |
| StackPop(); |
| return SUCCESS; |
| } |
| |
| JsonStringifier::Result JsonStringifier::SerializeJSReceiverSlow( |
| Handle<JSReceiver> object) { |
| Handle<FixedArray> contents = property_list_; |
| if (contents.is_null()) { |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate_, contents, |
| KeyAccumulator::GetKeys(isolate_, object, KeyCollectionMode::kOwnOnly, |
| ENUMERABLE_STRINGS, |
| GetKeysConversion::kConvertToString), |
| EXCEPTION); |
| } |
| AppendCharacter('{'); |
| Indent(); |
| bool comma = false; |
| for (int i = 0; i < contents->length(); i++) { |
| Handle<String> key(Cast<String>(contents->get(i)), isolate_); |
| Handle<Object> property; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate_, property, Object::GetPropertyOrElement(isolate_, object, key), |
| EXCEPTION); |
| Result result = SerializeProperty(property, comma, key); |
| if (!comma && result == SUCCESS) comma = true; |
| if (result == EXCEPTION || result == NEED_STACK) return result; |
| } |
| Unindent(); |
| if (comma) NewLine(); |
| AppendCharacter('}'); |
| return SUCCESS; |
| } |
| |
| JsonStringifier::Result JsonStringifier::SerializeJSProxy( |
| Handle<JSProxy> object, Handle<Object> key) { |
| HandleScope scope(isolate_); |
| Result stack_push = StackPush(object, key); |
| if (stack_push != SUCCESS) return stack_push; |
| Maybe<bool> is_array = Object::IsArray(object); |
| if (is_array.IsNothing()) return EXCEPTION; |
| if (is_array.FromJust()) { |
| Handle<Object> length_object; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate_, length_object, |
| Object::GetLengthFromArrayLike(isolate_, Cast<JSReceiver>(object)), |
| EXCEPTION); |
| uint32_t length; |
| if (!Object::ToUint32(*length_object, &length)) { |
| // Technically, we need to be able to handle lengths outside the |
| // uint32_t range. However, we would run into string size overflow |
| // if we tried to stringify such an array. |
| isolate_->Throw(*isolate_->factory()->NewInvalidStringLengthError()); |
| return EXCEPTION; |
| } |
| AppendCharacter('['); |
| Indent(); |
| Result result = SerializeArrayLikeSlow(object, 0, length); |
| if (result != SUCCESS) return result; |
| Unindent(); |
| if (length > 0) NewLine(); |
| AppendCharacter(']'); |
| } else { |
| Result result = SerializeJSReceiverSlow(object); |
| if (result != SUCCESS) return result; |
| } |
| StackPop(); |
| return SUCCESS; |
| } |
| |
| template <typename SrcChar, typename DestChar, bool raw_json> |
| bool JsonStringifier::SerializeStringUnchecked_( |
| base::Vector<const SrcChar> src, NoExtendBuilder<DestChar>* dest) { |
| // Assert that base::uc16 character is not truncated down to 8 bit. |
| // The <base::uc16, char> version of this method must not be called. |
| DCHECK(sizeof(DestChar) >= sizeof(SrcChar)); |
| bool required_escaping = false; |
| for (int i = 0; i < src.length(); i++) { |
| SrcChar c = src[i]; |
| if (raw_json || DoNotEscape(c)) { |
| dest->Append(c); |
| } else if (sizeof(SrcChar) != 1 && |
| base::IsInRange(c, static_cast<SrcChar>(0xD800), |
| static_cast<SrcChar>(0xDFFF))) { |
| // The current character is a surrogate. |
| required_escaping = true; |
| if (c <= 0xDBFF) { |
| // The current character is a leading surrogate. |
| if (i + 1 < src.length()) { |
| // There is a next character. |
| SrcChar next = src[i + 1]; |
| if (base::IsInRange(next, static_cast<SrcChar>(0xDC00), |
| static_cast<SrcChar>(0xDFFF))) { |
| // The next character is a trailing surrogate, meaning this is a |
| // surrogate pair. |
| dest->Append(c); |
| dest->Append(next); |
| i++; |
| } else { |
| // The next character is not a trailing surrogate. Thus, the |
| // current character is a lone leading surrogate. |
| dest->AppendCString("\\u"); |
| char* const hex = DoubleToRadixCString(c, 16); |
| dest->AppendCString(hex); |
| DeleteArray(hex); |
| } |
| } else { |
| // There is no next character. Thus, the current character is a lone |
| // leading surrogate. |
| dest->AppendCString("\\u"); |
| char* const hex = DoubleToRadixCString(c, 16); |
| dest->AppendCString(hex); |
| DeleteArray(hex); |
| } |
| } else { |
| // The current character is a lone trailing surrogate. (If it had been |
| // preceded by a leading surrogate, we would've ended up in the other |
| // branch earlier on, and the current character would've been handled |
| // as part of the surrogate pair already.) |
| dest->AppendCString("\\u"); |
| char* const hex = DoubleToRadixCString(c, 16); |
| dest->AppendCString(hex); |
| DeleteArray(hex); |
| } |
| } else { |
| DCHECK_LT(c, 0x60); |
| required_escaping = true; |
| dest->AppendCString(&JsonEscapeTable[c * kJsonEscapeTableEntrySize]); |
| } |
| } |
| return required_escaping; |
| } |
| |
| template <typename SrcChar, typename DestChar, bool raw_json> |
| bool JsonStringifier::SerializeString_(Tagged<String> string, |
| const DisallowGarbageCollection& no_gc) { |
| int length = string->length(); |
| bool required_escaping = false; |
| if (!raw_json) Append<uint8_t, DestChar>('"'); |
| // We might be able to fit the whole escaped string in the current string |
| // part, or we might need to allocate. |
| base::Vector<const SrcChar> vector = string->GetCharVector<SrcChar>(no_gc); |
| if V8_LIKELY (EscapedLengthIfCurrentPartFits(length)) { |
| NoExtendBuilder<DestChar> no_extend( |
| reinterpret_cast<DestChar*>(part_ptr_) + current_index_, |
| ¤t_index_); |
| required_escaping = SerializeStringUnchecked_<SrcChar, DestChar, raw_json>( |
| vector, &no_extend); |
| } else { |
| for (int i = 0; i < vector.length(); i++) { |
| SrcChar c = vector.at(i); |
| if (raw_json || DoNotEscape(c)) { |
| Append<SrcChar, DestChar>(c); |
| } else if (sizeof(SrcChar) != 1 && |
| base::IsInRange(c, static_cast<SrcChar>(0xD800), |
| static_cast<SrcChar>(0xDFFF))) { |
| // The current character is a surrogate. |
| required_escaping = true; |
| if (c <= 0xDBFF) { |
| // The current character is a leading surrogate. |
| if (i + 1 < vector.length()) { |
| // There is a next character. |
| SrcChar next = vector.at(i + 1); |
| if (base::IsInRange(next, static_cast<SrcChar>(0xDC00), |
| static_cast<SrcChar>(0xDFFF))) { |
| // The next character is a trailing surrogate, meaning this is a |
| // surrogate pair. |
| Append<SrcChar, DestChar>(c); |
| Append<SrcChar, DestChar>(next); |
| i++; |
| } else { |
| // The next character is not a trailing surrogate. Thus, the |
| // current character is a lone leading surrogate. |
| AppendCStringLiteral("\\u"); |
| char* const hex = DoubleToRadixCString(c, 16); |
| AppendCString(hex); |
| DeleteArray(hex); |
| } |
| } else { |
| // There is no next character. Thus, the current character is a |
| // lone leading surrogate. |
| AppendCStringLiteral("\\u"); |
| char* const hex = DoubleToRadixCString(c, 16); |
| AppendCString(hex); |
| DeleteArray(hex); |
| } |
| } else { |
| // The current character is a lone trailing surrogate. (If it had |
| // been preceded by a leading surrogate, we would've ended up in the |
| // other branch earlier on, and the current character would've been |
| // handled as part of the surrogate pair already.) |
| AppendCStringLiteral("\\u"); |
| char* const hex = DoubleToRadixCString(c, 16); |
| AppendCString(hex); |
| DeleteArray(hex); |
| } |
| } else { |
| DCHECK_LT(c, 0x60); |
| required_escaping = true; |
| AppendCString(&JsonEscapeTable[c * kJsonEscapeTableEntrySize]); |
| } |
| } |
| } |
| if (!raw_json) Append<uint8_t, DestChar>('"'); |
| return required_escaping; |
| } |
| |
| template <typename DestChar> |
| bool JsonStringifier::TrySerializeSimplePropertyKey( |
| Tagged<String> key, const DisallowGarbageCollection& no_gc) { |
| ReadOnlyRoots roots(isolate_); |
| if (key->map() != roots.internalized_one_byte_string_map()) { |
| return false; |
| } |
| if (!key_cache_.Contains(key)) { |
| return false; |
| } |
| int length = key->length(); |
| int copy_length = length; |
| if constexpr (sizeof(DestChar) == 1) { |
| // CopyChars has fast paths for small integer lengths, and is generally a |
| // little faster if we round the length up to the nearest 4. This is still |
| // within the bounds of the object on the heap, because object alignment is |
| // never less than 4 for any build configuration. |
| constexpr int kRounding = 4; |
| static_assert(kRounding <= kObjectAlignment); |
| copy_length = RoundUp(length, kRounding); |
| } |
| // Add three for the quote marks and colon, to determine how much output space |
| // is needed. We might actually require a little less output space than this, |
| // depending on how much rounding happened above, but it's more important to |
| // compute the requirement quickly than to be precise. |
| int required_length = copy_length + 3; |
| if (!CurrentPartCanFit(required_length)) { |
| return false; |
| } |
| NoExtendBuilder<DestChar> no_extend( |
| reinterpret_cast<DestChar*>(part_ptr_) + current_index_, ¤t_index_); |
| no_extend.Append('"'); |
| base::Vector<const uint8_t> chars( |
| Cast<SeqOneByteString>(key)->GetChars(no_gc), copy_length); |
| DCHECK_LE(reinterpret_cast<Address>(chars.end()), |
| key.address() + key->Size()); |
| #if DEBUG |
| for (int i = 0; i < length; ++i) { |
| DCHECK(DoNotEscape(chars[i])); |
| } |
| #endif // DEBUG |
| no_extend.AppendChars(chars, length); |
| no_extend.Append('"'); |
| no_extend.Append(':'); |
| return true; |
| } |
| |
| template <> |
| bool JsonStringifier::DoNotEscape(uint8_t c) { |
| // https://tc39.github.io/ecma262/#table-json-single-character-escapes |
| return JsonDoNotEscapeFlagTable[c]; |
| } |
| |
| template <> |
| bool JsonStringifier::DoNotEscape(uint16_t c) { |
| // https://tc39.github.io/ecma262/#table-json-single-character-escapes |
| return (c >= 0x20 && c <= 0x21) || |
| (c >= 0x23 && c != 0x5C && (c < 0xD800 || c > 0xDFFF)); |
| } |
| |
| void JsonStringifier::NewLine() { |
| if (gap_ == nullptr) return; |
| NewLineOutline(); |
| } |
| |
| void JsonStringifier::NewLineOutline() { |
| AppendCharacter('\n'); |
| for (int i = 0; i < indent_; i++) AppendCString(gap_); |
| } |
| |
| void JsonStringifier::Separator(bool first) { |
| if (!first) AppendCharacter(','); |
| NewLine(); |
| } |
| |
| void JsonStringifier::SerializeDeferredKey(bool deferred_comma, |
| Handle<Object> deferred_key) { |
| Separator(!deferred_comma); |
| Handle<String> string_key = Cast<String>(deferred_key); |
| bool wrote_simple = false; |
| { |
| DisallowGarbageCollection no_gc; |
| wrote_simple = |
| encoding_ == String::ONE_BYTE_ENCODING |
| ? TrySerializeSimplePropertyKey<uint8_t>(*string_key, no_gc) |
| : TrySerializeSimplePropertyKey<base::uc16>(*string_key, no_gc); |
| } |
| |
| if (!wrote_simple) { |
| bool required_escaping = SerializeString<false>(string_key); |
| if (!required_escaping) { |
| key_cache_.TryInsert(*string_key); |
| } |
| AppendCharacter(':'); |
| } |
| |
| if (gap_ != nullptr) AppendCharacter(' '); |
| } |
| |
| template <bool raw_json> |
| bool JsonStringifier::SerializeString(Handle<String> object) { |
| object = String::Flatten(isolate_, object); |
| DisallowGarbageCollection no_gc; |
| auto string = *object; |
| if (encoding_ == String::ONE_BYTE_ENCODING) { |
| if (String::IsOneByteRepresentationUnderneath(string)) { |
| return SerializeString_<uint8_t, uint8_t, raw_json>(string, no_gc); |
| } else { |
| ChangeEncoding(); |
| } |
| } |
| DCHECK_EQ(encoding_, String::TWO_BYTE_ENCODING); |
| if (String::IsOneByteRepresentationUnderneath(string)) { |
| return SerializeString_<uint8_t, base::uc16, raw_json>(string, no_gc); |
| } else { |
| return SerializeString_<base::uc16, base::uc16, raw_json>(string, no_gc); |
| } |
| } |
| |
| void JsonStringifier::Extend() { |
| if (part_length_ >= String::kMaxLength) { |
| // Set the flag and carry on. Delay throwing the exception till the end. |
| current_index_ = 0; |
| overflowed_ = true; |
| return; |
| } |
| part_length_ *= kPartLengthGrowthFactor; |
| if (encoding_ == String::ONE_BYTE_ENCODING) { |
| uint8_t* tmp_ptr = new uint8_t[part_length_]; |
| memcpy(tmp_ptr, one_byte_ptr_, current_index_); |
| if (one_byte_ptr_ != one_byte_array_) delete[] one_byte_ptr_; |
| one_byte_ptr_ = tmp_ptr; |
| part_ptr_ = one_byte_ptr_; |
| } else { |
| base::uc16* tmp_ptr = new base::uc16[part_length_]; |
| for (int i = 0; i < current_index_; i++) { |
| tmp_ptr[i] = two_byte_ptr_[i]; |
| } |
| delete[] two_byte_ptr_; |
| two_byte_ptr_ = tmp_ptr; |
| part_ptr_ = two_byte_ptr_; |
| } |
| } |
| |
| void JsonStringifier::ChangeEncoding() { |
| encoding_ = String::TWO_BYTE_ENCODING; |
| two_byte_ptr_ = new base::uc16[part_length_]; |
| for (int i = 0; i < current_index_; i++) { |
| two_byte_ptr_[i] = one_byte_ptr_[i]; |
| } |
| part_ptr_ = two_byte_ptr_; |
| if (one_byte_ptr_ != one_byte_array_) delete[] one_byte_ptr_; |
| one_byte_ptr_ = nullptr; |
| } |
| |
| } // namespace internal |
| } // namespace v8 |