blob: 30aade074b33d0f57562190bd217d7388fe9ee3c [file] [log] [blame] [edit]
// 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 <string_view>
#include "absl/functional/overload.h"
#include "hwy/highway.h"
#include "src/base/strings.h"
#include "src/common/assert-scope.h"
#include "src/common/globals.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 {
static constexpr char kJsonStringifierZoneName[] = "json-stringifier-zone";
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 MaybeDirectHandle<Object> Stringify(
Handle<JSAny> object, Handle<JSAny> replacer, Handle<Object> gap);
private:
enum Result { UNCHANGED, SUCCESS, EXCEPTION, NEED_STACK };
bool InitializeReplacer(Handle<JSAny> replacer);
bool InitializeGap(Handle<Object> gap);
V8_WARN_UNUSED_RESULT MaybeHandle<JSAny> ApplyToJsonFunction(
Handle<JSAny> object, DirectHandle<Object> key);
V8_WARN_UNUSED_RESULT MaybeHandle<JSAny> ApplyReplacerFunction(
Handle<JSAny> value, DirectHandle<Object> key,
DirectHandle<Object> initial_holder);
// Entry point to serialize the object.
V8_INLINE Result SerializeObject(Handle<JSAny> 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<JSAny> object,
int i) {
return Serialize_<false>(object, false,
Handle<Object>(Smi::FromInt(i), isolate));
}
// Serialize an 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<JSAny> 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 <size_t N>
V8_INLINE void AppendCStringLiteral(const char (&literal)[N]) {
// Note that the literal contains the zero char.
constexpr size_t 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++);
}
}
template <typename SrcChar>
V8_INLINE void AppendString(std::basic_string_view<SrcChar> s) {
if (encoding_ == String::ONE_BYTE_ENCODING) {
for (SrcChar c : s) Append<SrcChar, uint8_t>(c);
} else {
for (SrcChar c : s) Append<SrcChar, base::uc16>(c);
}
}
V8_INLINE bool CurrentPartCanFit(size_t 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 left by 3
// is a more pessimistic estimate, but faster to calculate.
V8_INLINE bool EscapedLengthIfCurrentPartFits(size_t length) {
if (length > kMaxPartLength) return false;
static_assert(kMaxPartLength <= (String::kMaxLength >> 3));
// This shift will not overflow because length is already less than the
// maximum part length.
return CurrentPartCanFit(length << 3);
}
void AppendStringByCopy(Tagged<String> string, size_t length,
const DisallowGarbageCollection& no_gc) {
DCHECK_EQ(length, string->length());
DCHECK(encoding_ == String::TWO_BYTE_ENCODING ||
(string->IsFlat() &&
String::IsOneByteRepresentationUnderneath(string)));
DCHECK(CurrentPartCanFit(length + 1));
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(), length);
} else {
ChangeEncoding();
CopyChars<uint16_t, uint16_t>(
two_byte_ptr_ + current_index_,
string->GetCharVector<uint16_t>(no_gc).begin(), length);
}
} else {
if (String::IsOneByteRepresentationUnderneath(string)) {
CopyChars<uint8_t, uint16_t>(
two_byte_ptr_ + current_index_,
string->GetCharVector<uint8_t>(no_gc).begin(), length);
} else {
CopyChars<uint16_t, uint16_t>(
two_byte_ptr_ + current_index_,
string->GetCharVector<uint16_t>(no_gc).begin(), length);
}
}
current_index_ += length;
DCHECK(current_index_ <= part_length_);
}
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) {
size_t length = string->length();
while (!CurrentPartCanFit(length + 1)) {
Extend();
if (V8_UNLIKELY(overflowed_)) return;
}
AppendStringByCopy(string, length, no_gc);
return;
}
}
SerializeString<true>(string_handle);
}
template <typename SrcChar>
void AppendSubstringByCopy(const SrcChar* src, size_t count) {
DCHECK(CurrentPartCanFit(count + 1));
if (encoding_ == String::ONE_BYTE_ENCODING) {
if (sizeof(SrcChar) == 1) {
CopyChars<SrcChar, uint8_t>(one_byte_ptr_ + current_index_, src, count);
} else {
ChangeEncoding();
CopyChars<SrcChar, base::uc16>(two_byte_ptr_ + current_index_, src,
count);
}
} else {
CopyChars<SrcChar, base::uc16>(two_byte_ptr_ + current_index_, src,
count);
}
current_index_ += count;
DCHECK_LE(current_index_, part_length_);
}
template <typename SrcChar>
V8_NOINLINE void AppendSubstring(const SrcChar* src, size_t from, size_t to) {
if (from == to) return;
DCHECK_LT(from, to);
size_t count = to - from;
while (!CurrentPartCanFit(count + 1)) {
Extend();
if (V8_UNLIKELY(overflowed_)) return;
}
AppendSubstringByCopy(src + from, count);
}
bool HasValidCurrentIndex() const { return current_index_ < part_length_; }
template <bool deferred_string_key>
Result Serialize_(Handle<JSAny> 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(DirectHandle<JSReceiver> object);
template <ElementsKind kind>
V8_INLINE Result SerializeFixedArrayWithInterruptCheck(
DirectHandle<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(DirectHandle<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, size_t* current_index)
: current_index_(current_index), start_(start), cursor_(start) {}
~NoExtendBuilder() { *current_index_ += 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++));
}
V8_INLINE void AppendString(std::string_view str) {
AppendChars(
base::Vector<const uint8_t>(
reinterpret_cast<const uint8_t*>(str.data()), str.length()),
str.length());
}
// 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;
}
template <typename SrcChar>
V8_INLINE void AppendSubstring(const SrcChar* src, size_t from, size_t to) {
if (from == to) return;
DCHECK_LT(from, to);
size_t count = to - from;
CopyChars(cursor_, src + from, count);
cursor_ += count;
}
private:
size_t* 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);
V8_INLINE void NewLine();
V8_NOINLINE void NewLineOutline();
V8_INLINE void Indent() { indent_++; }
V8_INLINE void Unindent() { indent_--; }
V8_INLINE void Separator(bool first);
DirectHandle<JSReceiver> CurrentHolder(DirectHandle<Object> value,
DirectHandle<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 constexpr int kCircularErrorMessagePrefixCount = 2;
static constexpr int kCircularErrorMessagePostfixCount = 1;
static constexpr size_t kInitialPartLength = 2048;
static constexpr size_t kMaxPartLength = 16 * 1024;
static constexpr size_t 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_;
size_t part_length_;
size_t 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];
};
namespace {
constexpr int kJsonEscapeTableEntrySize = 8;
// Translation table to escape Latin1 characters.
// Table entries start at a multiple of 8 and are null-terminated.
constexpr const char* const 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 ";
// LINT.IfChange(StringDoesNotContainEscapeCharacters)
constexpr bool 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,
};
template <typename Char>
constexpr bool DoNotEscape(Char c);
template <>
constexpr bool DoNotEscape(uint8_t c) {
// https://tc39.github.io/ecma262/#table-json-single-character-escapes
return JsonDoNotEscapeFlagTable[c];
}
template <>
constexpr bool 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));
}
// Checks if characters need escaping in a packed input (4 bytes in uint32_t).
constexpr bool NeedsEscape(uint32_t input) {
constexpr uint32_t mask_0x20 = 0x20202020u;
constexpr uint32_t mask_0x22 = 0x22222222u;
constexpr uint32_t mask_0x5c = 0x5C5C5C5Cu;
constexpr uint32_t mask_0x01 = 0x01010101u;
constexpr uint32_t mask_msb = 0x80808080u;
// Escape control characters (< 0x20).
const uint32_t has_lt_0x20 = input - mask_0x20;
// Escape double quotation mark (0x22).
const uint32_t has_0x22 = (input ^ mask_0x22) - mask_0x01;
// Escape backslash (0x5C).
const uint32_t has_0x5c = (input ^ mask_0x5c) - mask_0x01;
// Chars >= 0x7F don't need escaping.
const uint32_t result_mask = ~input & mask_msb;
const uint32_t result = ((has_lt_0x20 | has_0x22 | has_0x5c) & result_mask);
return result != 0;
}
// LINT.ThenChange(/src/objects/string.cc:StringDoesNotContainEscapeCharacters)
template <typename SrcChar>
requires(sizeof(SrcChar) == sizeof(uint8_t))
bool DoNotEscape(const SrcChar* chars, size_t length,
const DisallowGarbageCollection& no_gc) {
bool no_escape = true;
using PackedT = uint32_t;
static constexpr size_t stride = sizeof(PackedT);
size_t i = 0;
for (; i + (stride - 1) < length; i += stride) {
PackedT packed = *reinterpret_cast<const PackedT*>(chars + i);
if (V8_UNLIKELY(NeedsEscape(packed))) break;
}
for (; i < length; i++) {
no_escape = no_escape && DoNotEscape(chars[i]);
}
return no_escape;
}
bool IsFastKey(Tagged<String> key, const DisallowGarbageCollection& no_gc) {
return key->DispatchToSpecificType(
absl::Overload{[&](Tagged<SeqOneByteString> str) {
const uint8_t* chars = str->GetChars(no_gc);
return DoNotEscape(chars, str->length(), no_gc);
},
[&](Tagged<ExternalOneByteString> str) {
const uint8_t* chars = str->GetChars();
return DoNotEscape(chars, str->length(), no_gc);
},
[&](Tagged<String> str) { return false; }});
}
bool CanFastSerializeJSArray(Isolate* isolate, Tagged<JSArray> object) {
// If the no elements protector is intact, Array.prototype and
// Object.prototype are guaranteed to not have elements in any native context.
if (!Protectors::IsNoElementsIntact(isolate)) return false;
Tagged<Map> map = object->map(isolate);
Tagged<NativeContext> native_context = map->map(isolate)->native_context();
Tagged<HeapObject> proto = map->prototype();
return native_context->GetNoCell(Context::INITIAL_ARRAY_PROTOTYPE_INDEX) ==
proto;
}
V8_INLINE bool CanFastSerializeJSObject(Tagged<JSObject> raw_object,
Isolate* isolate) {
DisallowGarbageCollection no_gc;
if (IsCustomElementsReceiverMap(raw_object->map())) return false;
if (!raw_object->HasFastProperties()) return false;
auto roots = ReadOnlyRoots(isolate);
auto elements = raw_object->elements();
return elements == roots.empty_fixed_array() ||
elements == roots.empty_slow_element_dictionary();
}
} // namespace
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_;
}
MaybeDirectHandle<Object> JsonStringifier::Stringify(Handle<JSAny> object,
Handle<JSAny> replacer,
Handle<Object> gap) {
if (!InitializeReplacer(replacer)) {
CHECK(isolate_->has_exception());
return MaybeDirectHandle<Object>();
}
if (!IsUndefined(*gap, isolate_) && !InitializeGap(gap)) {
CHECK(isolate_->has_exception());
return MaybeDirectHandle<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 MaybeDirectHandle<Object>();
}
bool JsonStringifier::InitializeReplacer(Handle<JSAny> 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();
DirectHandle<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) {
uint32_t gap_length = std::min(gap_string->length(), 10u);
gap_ = NewArray<base::uc16>(gap_length + 1);
String::WriteToFlat(*gap_string, gap_, 0, gap_length);
for (uint32_t 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) {
uint32_t gap_length = DoubleToUint32(value);
gap_ = NewArray<base::uc16>(gap_length + 1);
for (uint32_t i = 0; i < gap_length; i++) gap_[i] = ' ';
gap_[gap_length] = '\0';
}
}
return true;
}
MaybeHandle<JSAny> JsonStringifier::ApplyToJsonFunction(
Handle<JSAny> object, DirectHandle<Object> key) {
HandleScope scope(isolate_);
// Retrieve toJSON function. The LookupIterator automatically handles
// the ToObject() equivalent ("GetRoot") if {object} is a BigInt.
DirectHandle<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()->SmiToString(Cast<Smi>(*key));
DirectHandle<Object> args[] = {key};
ASSIGN_RETURN_ON_EXCEPTION(isolate_, object,
Cast<JSAny>(Execution::Call(
isolate_, fun, object, base::VectorOf(args))));
return scope.CloseAndEscape(object);
}
MaybeHandle<JSAny> JsonStringifier::ApplyReplacerFunction(
Handle<JSAny> value, DirectHandle<Object> key,
DirectHandle<Object> initial_holder) {
HandleScope scope(isolate_);
if (IsSmi(*key)) key = factory()->SmiToString(Cast<Smi>(*key));
DirectHandle<Object> args[] = {key, value};
DirectHandle<JSReceiver> holder = CurrentHolder(value, initial_holder);
ASSIGN_RETURN_ON_EXCEPTION(
isolate_, value,
Cast<JSAny>(Execution::Call(isolate_, replacer_function_, holder,
base::VectorOf(args))));
return scope.CloseAndEscape(value);
}
DirectHandle<JSReceiver> JsonStringifier::CurrentHolder(
DirectHandle<Object> value, DirectHandle<Object> initial_holder) {
if (stack_.empty()) {
DirectHandle<JSObject> holder =
factory()->NewJSObject(isolate_->object_function());
JSObject::AddProperty(isolate_, holder, factory()->empty_string(),
initial_holder, NONE);
return holder;
} else {
return DirectHandle<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(DirectHandle<Object> start_object) {
builder_.AppendCString(kStartPrefix);
builder_.AppendCStringLiteral("starting at object with constructor ");
AppendConstructorName(start_object);
}
void AppendNormalLine(DirectHandle<Object> key, DirectHandle<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(DirectHandle<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_assert(Smi::kMaxValue <= 2147483647);
static_assert(Smi::kMinValue >= -2147483648);
// sizeof(string) includes \0.
static constexpr uint32_t kBufferSize = sizeof("-2147483648") - 1;
char chars[kBufferSize];
base::Vector<char> buffer(chars, kBufferSize);
builder_.AppendString(IntToStringView(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<JSAny> object,
bool comma,
Handle<Object> key) {
StackLimitCheck interrupt_check(isolate_);
if (interrupt_check.InterruptRequested() &&
IsException(isolate_->stack_guard()->HandleInterrupts(), isolate_)) {
return EXCEPTION;
}
DirectHandle<JSAny> 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);
{
DirectHandle<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)) {
DirectHandle<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_assert(Smi::kMaxValue <= 2147483647);
static_assert(Smi::kMinValue >= -2147483648);
// sizeof(string) includes \0.
static constexpr uint32_t kBufferSize = sizeof("-2147483648") - 1;
char chars[kBufferSize];
base::Vector<char> buffer(chars, kBufferSize);
AppendString(IntToStringView(object.value(), buffer));
return SUCCESS;
}
JsonStringifier::Result JsonStringifier::SerializeDouble(double number) {
if (std::isinf(number) || std::isnan(number)) {
AppendCStringLiteral("null");
return SUCCESS;
}
static constexpr uint32_t kBufferSize = 100;
char chars[kBufferSize];
base::Vector<char> buffer(chars, kBufferSize);
std::string_view str = DoubleToStringView(number, buffer);
AppendString(str);
return SUCCESS;
}
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(
DirectHandle<JSArray> array, uint32_t length, uint32_t* slow_path_index) {
static_assert(IsSmiElementsKind(kind) || IsDoubleElementsKind(kind));
using ArrayT = std::conditional_t<IsDoubleElementsKind(kind),
FixedDoubleArray, FixedArray>;
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 ? !CanFastSerializeJSArray(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 (!CanFastSerializeJSArray(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(Cast<JSAny>(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(
DirectHandle<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 constexpr uint32_t 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_, Cast<JSAny>(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;
}
JsonStringifier::Result JsonStringifier::SerializeJSObject(
Handle<JSObject> object, Handle<Object> key) {
PtrComprCageBase cage_base(isolate_);
HandleScope handle_scope(isolate_);
if (!property_list_.is_null() ||
!CanFastSerializeJSObject(*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<JSAny> 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,
Cast<JSAny>(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(
DirectHandle<JSReceiver> object) {
DirectHandle<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(Cast<JSAny>(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()) {
DirectHandle<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;
size_t uncopied_src_index = 0; // Index of first char not copied yet.
for (size_t i = 0; i < src.size(); i++) {
SrcChar c = src[i];
if (raw_json || DoNotEscape(c)) {
continue;
} 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;
dest->AppendSubstring(src.data(), uncopied_src_index, i);
char double_to_radix_chars[kDoubleToRadixMaxChars];
base::Vector<char> double_to_radix_buffer =
base::ArrayVector(double_to_radix_chars);
if (c <= 0xDBFF) {
// The current character is a leading surrogate.
if (i + 1 < src.size()) {
// 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");
std::string_view hex =
DoubleToRadixStringView(c, 16, double_to_radix_buffer);
dest->AppendString(hex);
}
} else {
// There is no next character. Thus, the current character is a lone
// leading surrogate.
dest->AppendCString("\\u");
std::string_view hex =
DoubleToRadixStringView(c, 16, double_to_radix_buffer);
dest->AppendString(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");
std::string_view hex =
DoubleToRadixStringView(c, 16, double_to_radix_buffer);
dest->AppendString(hex);
}
uncopied_src_index = i + 1;
} else {
required_escaping = true;
dest->AppendSubstring(src.data(), uncopied_src_index, i);
DCHECK_LT(c, 0x60);
dest->AppendCString(&JsonEscapeTable[c * kJsonEscapeTableEntrySize]);
uncopied_src_index = i + 1;
}
}
dest->AppendSubstring(src.data(), uncopied_src_index, src.size());
return required_escaping;
}
template <typename SrcChar, typename DestChar, bool raw_json>
bool JsonStringifier::SerializeString_(Tagged<String> string,
const DisallowGarbageCollection& no_gc) {
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(vector.size())) {
NoExtendBuilder<DestChar> no_extend(
reinterpret_cast<DestChar*>(part_ptr_) + current_index_,
&current_index_);
required_escaping = SerializeStringUnchecked_<SrcChar, DestChar, raw_json>(
vector, &no_extend);
} else {
DCHECK(encoding_ == String::TWO_BYTE_ENCODING ||
(string->IsFlat() &&
String::IsOneByteRepresentationUnderneath(string)));
size_t uncopied_src_index = 0; // Index of first char not copied yet.
for (size_t i = 0; i < vector.size(); i++) {
SrcChar c = vector.at(i);
if (raw_json || DoNotEscape(c)) {
continue;
} 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;
AppendSubstring(vector.data(), uncopied_src_index, i);
char double_to_radix_chars[kDoubleToRadixMaxChars];
base::Vector<char> double_to_radix_buffer =
base::ArrayVector(double_to_radix_chars);
if (c <= 0xDBFF) {
// The current character is a leading surrogate.
if (i + 1 < vector.size()) {
// 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");
std::string_view hex =
DoubleToRadixStringView(c, 16, double_to_radix_buffer);
AppendString(hex);
}
} else {
// There is no next character. Thus, the current character is a
// lone leading surrogate.
AppendCStringLiteral("\\u");
std::string_view hex =
DoubleToRadixStringView(c, 16, double_to_radix_buffer);
AppendString(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");
std::string_view hex =
DoubleToRadixStringView(c, 16, double_to_radix_buffer);
AppendString(hex);
}
uncopied_src_index = i + 1;
} else {
required_escaping = true;
AppendSubstring(vector.data(), uncopied_src_index, i);
DCHECK_LT(c, 0x60);
AppendCString(&JsonEscapeTable[c * kJsonEscapeTableEntrySize]);
uncopied_src_index = i + 1;
}
}
AppendSubstring(vector.data(), uncopied_src_index, vector.size());
}
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;
}
size_t length = key->length();
size_t 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.
size_t required_length = copy_length + 3;
if (!CurrentPartCanFit(required_length)) {
return false;
}
NoExtendBuilder<DestChar> no_extend(
reinterpret_cast<DestChar*>(part_ptr_) + current_index_, &current_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 (size_t i = 0; i < length; ++i) {
DCHECK(DoNotEscape(chars[i]));
}
#endif // DEBUG
no_extend.AppendChars(chars, length);
no_extend.Append('"');
no_extend.Append(':');
return true;
}
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 (uint32_t 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 (uint32_t 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;
}
template <typename Char>
class OutBuffer {
public:
explicit OutBuffer(AccountingAllocator* allocator) : allocator_(allocator) {
cur_ = stack_buffer_;
segment_end_ = cur_ + kStackBufferSize;
}
template <typename SrcChar>
requires(sizeof(Char) >= sizeof(SrcChar))
V8_INLINE void AppendCharacter(SrcChar c) {
ReduceCurrentCapacity(1);
DCHECK_GE(SegmentFreeChars(), 1);
*cur_++ = c;
}
template <typename SrcChar>
requires(sizeof(Char) >= sizeof(SrcChar))
void Append(const SrcChar* chars, size_t length) {
ReduceCurrentCapacity(length);
DCHECK_GE(SegmentFreeChars(), length);
CopyChars(cur_, chars, length);
cur_ += length;
}
void EnsureCapacity(size_t size) {
#ifdef DEBUG
current_requested_capacity_ = size;
#endif
if (V8_LIKELY(size <= SegmentFreeChars())) return;
Extend(size);
DCHECK_GE(CurSegmentCapacity(), size);
}
size_t length() const {
if (ZoneUsed()) {
DCHECK_GT(segments_->length(), 0);
size_t length = stack_buffer_size_;
for (int i = 0; i < segments_->length() - 1; i++) {
length += segments_->at(i).size();
}
length += CurSegmentLength();
return length;
} else {
return StackBufferLength();
}
}
template <typename Dst>
void CopyTo(Dst* dst) {
if (ZoneUsed()) {
// Copy stack segment.
CopyChars(dst, stack_buffer_, stack_buffer_size_);
dst += stack_buffer_size_;
// Copy full segments.
DCHECK_GT(segments_->length(), 0);
for (int i = 0; i < segments_->length() - 1; i++) {
base::Vector<Char> segment = segments_.value()[i];
size_t segment_length = segment.size();
CopyChars(dst, segment.begin(), segment_length);
dst += segment_length;
}
// Copy last (partially filled) segment.
base::Vector<Char> segment = segments_->last();
CopyChars(dst, segment.begin(), CurSegmentLength());
} else {
// Copy (partially filled) stack segment.
CopyChars(dst, stack_buffer_, StackBufferLength());
}
}
private:
static constexpr uint32_t kInitialSegmentSize = 2 * KB;
static constexpr uint32_t kMaxSegmentSize = 32 * KB;
static_assert(base::bits::IsPowerOfTwo(kInitialSegmentSize));
static_assert(base::bits::IsPowerOfTwo(kMaxSegmentSize));
static constexpr uint8_t kInitialSegmentSizeHighestBit =
kBitsPerInt - base::bits::CountLeadingZeros32(kInitialSegmentSize) - 1;
static constexpr uint8_t kMaxSegmentSizeHighestBit =
kBitsPerInt - base::bits::CountLeadingZeros32(kMaxSegmentSize) - 1;
static constexpr uint8_t kNumVariableSegments =
kMaxSegmentSizeHighestBit - kInitialSegmentSizeHighestBit;
static constexpr size_t kStackBufferSize = 256;
V8_NOINLINE V8_PRESERVE_MOST void Extend(size_t min_size) {
if (ZoneUsed()) {
segments_->last().Truncate(CurSegmentLength());
} else {
stack_buffer_size_ = StackBufferLength();
zone_.emplace(allocator_, kJsonStringifierZoneName);
segments_.emplace(1, &zone_.value());
}
const size_t new_segment_size =
std::max(min_size, SegmentCapacity(segments_->length()));
segments_->Add(zone_->AllocateVector<Char>(new_segment_size),
&zone_.value());
cur_ = segments_->last().begin();
segment_end_ = segments_->last().end();
}
V8_INLINE size_t SegmentFreeChars() const { return segment_end_ - cur_; }
V8_INLINE size_t StackBufferLength() const {
DCHECK(!ZoneUsed());
return cur_ - stack_buffer_;
}
V8_INLINE size_t CurSegmentLength() const {
DCHECK(ZoneUsed());
return cur_ - segments_->last().begin();
}
V8_INLINE size_t SegmentCapacity(size_t segment) {
return 1u << std::min<size_t>(segment + kInitialSegmentSizeHighestBit,
kMaxSegmentSizeHighestBit);
}
V8_INLINE size_t CurSegmentCapacity() {
DCHECK(ZoneUsed());
DCHECK_GT(segments_->length(), 0);
return segments_->last().size();
}
V8_INLINE void ReduceCurrentCapacity(size_t size) {
#ifdef DEBUG
DCHECK_LE(size, current_requested_capacity_);
current_requested_capacity_ -= size;
#endif
}
V8_INLINE bool ZoneUsed() const { return zone_.has_value(); }
AccountingAllocator* allocator_;
Char stack_buffer_[kStackBufferSize];
size_t stack_buffer_size_;
Char* cur_;
Char* segment_end_;
std::optional<Zone> zone_;
std::optional<ZoneList<base::Vector<Char>>> segments_;
#ifdef DEBUG
size_t current_requested_capacity_;
#endif
};
enum FastJsonStringifierResult {
SUCCESS,
JS_OBJECT,
JS_ARRAY,
UNDEFINED,
CHANGE_ENCODING,
SLOW_PATH,
EXCEPTION
};
enum class FastJsonStringifierObjectKeyResult : uint8_t {
kSuccess,
kChangeEncoding, // Two-byte key in one-byte stringifier.
kSlow // Two-byte key (in two-byte stringifier) or requires escaping.
};
class ContinuationRecord {
public:
enum Type {
kObject,
kArray,
kObjectResume_FastIterable,
kObjectResume_SlowIterable,
kObjectResume_Uninitialized,
kArrayResume,
kArrayResume_Holey,
kArrayResume_WithInterrupts,
kArrayResume_Holey_WithInterrupts,
kSimpleObject, // Resume after encoding change.
// Object is any simple object that can be serialized
// successfully with TrySerializeSimpleObject().
kObjectKey // For encoding changes triggered by object keys.
// This is required (instead of simply resuming with the
// object/index that triggered the change), to avoid
// re-serializing '{' if the first property key triggered
// the change.
};
using ObjectT = UnionOf<JSAny, FixedArrayBase>;
static constexpr ContinuationRecord ForSimpleObject(Tagged<JSAny> obj) {
return ContinuationRecord(Type::kSimpleObject, obj, 0, 0);
}
static constexpr ContinuationRecord ForJSAny(
Tagged<JSAny> obj, FastJsonStringifierResult result) {
return ContinuationRecord(ContinuationTypeFromResult(result), obj, 0, 0);
}
static constexpr ContinuationRecord ForJSArray(Tagged<JSAny> obj) {
DCHECK(Is<JSArray>(obj));
return ContinuationRecord(Type::kArray, obj, 0, 0);
}
template <ElementsKind kind, bool with_interrupt_check>
static constexpr ContinuationRecord ForJSArrayResume(
Tagged<FixedArrayBase> obj, uint32_t index, uint32_t length) {
return ContinuationRecord(
ContinuationTypeForArray(kind, with_interrupt_check), obj, index,
length);
}
static constexpr ContinuationRecord ForJSObject(Tagged<JSAny> obj) {
DCHECK(Is<JSObject>(obj));
return ContinuationRecord(Type::kObject, obj, 0, 0, 0, 0,
Tagged<DescriptorArray>());
}
template <DescriptorArray::FastIterableState fast_iterable_state>
static constexpr ContinuationRecord ForJSObjectResume(
Tagged<JSAny> obj, uint16_t descriptor_idx, uint16_t nof_descriptors,
uint8_t in_object_properties, uint8_t in_object_properties_start,
Tagged<DescriptorArray> descriptors) {
DCHECK(Is<JSObject>(obj));
Type type;
using enum DescriptorArray::FastIterableState;
switch (fast_iterable_state) {
case kJsonFast:
type = Type::kObjectResume_FastIterable;
break;
case kJsonSlow:
type = Type::kObjectResume_SlowIterable;
break;
case kUnknown:
type = Type::kObjectResume_Uninitialized;
break;
}
return ContinuationRecord(type, obj, descriptor_idx, nof_descriptors,
in_object_properties, in_object_properties_start,
descriptors);
}
static constexpr ContinuationRecord ForObjectKey(Tagged<String> key,
bool comma) {
return ContinuationRecord(Type::kObjectKey, key, comma);
}
Type type() const { return type_; }
Tagged<ObjectT> object() const { return object_; }
Tagged<JSAny> simple_object() const {
DCHECK_EQ(type(), Type::kSimpleObject);
return Cast<JSAny>(object_);
}
Tagged<JSArray> js_array() const {
DCHECK_EQ(type(), Type::kArray);
return Cast<JSArray>(object_);
}
Tagged<FixedArrayBase> array_elements() const {
DCHECK(IsArrayResumeType(type()));
return Cast<FixedArrayBase>(object_);
}
Tagged<JSObject> js_object() const {
DCHECK(type() == Type::kObject || IsObjectResumeType(type()));
return Cast<JSObject>(object_);
}
Tagged<String> object_key() const {
DCHECK_EQ(type(), Type::kObjectKey);
return Cast<String>(object_);
}
uint32_t array_index() const {
DCHECK(IsArrayResumeType(type()));
return js_array_.index;
}
uint32_t array_length() const {
DCHECK(IsArrayResumeType(type()));
return js_array_.length;
}
uint16_t object_descriptor_idx() const {
DCHECK(IsObjectResumeType(type()));
return js_object_.descriptor_idx;
}
uint16_t object_nof_descriptors() const {
DCHECK(IsObjectResumeType(type()));
return js_object_.nof_descriptors;
}
uint8_t object_in_object_properties() const {
DCHECK(IsObjectResumeType(type()));
return js_object_.in_object_properties;
}
uint8_t object_in_object_properties_start() const {
DCHECK(IsObjectResumeType(type()));
return js_object_.in_object_properties_start;
}
Tagged<DescriptorArray> object_descriptors() const {
DCHECK(IsObjectResumeType(type()));
return js_object_.descriptors;
}
bool object_key_comma() const {
DCHECK_EQ(type(), Type::kObjectKey);
return object_key_.comma;
}
static constexpr bool IsObjectResumeType(Type type) {
return type == Type::kObjectResume_FastIterable ||
type == Type::kObjectResume_SlowIterable ||
type == Type::kObjectResume_Uninitialized;
}
static constexpr bool IsArrayResumeType(Type type) {
return type == Type::kArrayResume || type == Type::kArrayResume_Holey ||
type == Type::kArrayResume_WithInterrupts ||
type == Type::kArrayResume_Holey_WithInterrupts;
}
private:
constexpr ContinuationRecord(Type type, Tagged<ObjectT> obj, uint32_t index,
uint32_t length)
: type_(type), object_(obj), js_array_({index, length}) {}
constexpr ContinuationRecord(Type type, Tagged<ObjectT> obj,
uint16_t descriptor_idx,
uint16_t nof_descriptors,
uint8_t in_object_properties,
uint8_t in_object_properties_start,
Tagged<DescriptorArray> descriptors)
: type_(type),
object_(obj),
js_object_({descriptor_idx, nof_descriptors, in_object_properties,
in_object_properties_start, descriptors}) {}
constexpr ContinuationRecord(Type type, Tagged<ObjectT> obj, bool comma)
: type_(type), object_(obj), object_key_{comma} {}
static constexpr Type ContinuationTypeFromResult(
FastJsonStringifierResult result) {
DCHECK(result == JS_OBJECT || result == JS_ARRAY);
static_assert(JS_OBJECT - 1 == Type::kObject);
static_assert(JS_ARRAY - 1 == Type::kArray);
return static_cast<Type>(result - 1);
}
static consteval Type ContinuationTypeForArray(ElementsKind kind,
bool with_interrupt_check) {
DCHECK(IsObjectElementsKind(kind));
if (IsHoleyElementsKind(kind)) {
if (with_interrupt_check) {
return kArrayResume_Holey_WithInterrupts;
} else {
return kArrayResume_Holey;
}
} else {
if (with_interrupt_check) {
return kArrayResume_WithInterrupts;
} else {
return kArrayResume;
}
}
}
Type type_;
Tagged<ObjectT> object_;
union {
struct {
uint32_t index;
uint32_t length;
} js_array_;
struct {
uint16_t descriptor_idx;
uint16_t nof_descriptors;
uint8_t in_object_properties;
uint8_t in_object_properties_start;
Tagged<DescriptorArray> descriptors;
} js_object_;
struct {
bool comma;
} object_key_;
};
};
template <typename Char>
class FastJsonStringifier {
public:
explicit FastJsonStringifier(Isolate* isolate);
size_t ResultLength() const { return buffer_.length(); }
template <typename DstChar>
void CopyResultTo(DstChar* out_buffer) {
buffer_.CopyTo(out_buffer);
}
V8_INLINE FastJsonStringifierResult
SerializeObject(Tagged<JSAny> object, const DisallowGarbageCollection& no_gc);
template <typename OldChar>
requires(sizeof(OldChar) < sizeof(Char))
V8_NOINLINE FastJsonStringifierResult
ResumeFrom(FastJsonStringifier<OldChar>& old_stringifier,
const DisallowGarbageCollection& no_gc);
private:
static constexpr bool is_one_byte = sizeof(Char) == sizeof(uint8_t);
V8_INLINE void SeparatorUnchecked(bool comma) {
if (comma) AppendCharacterUnchecked(',');
}
V8_INLINE void Separator(bool comma) {
if (comma) AppendCharacter(',');
}
V8_INLINE void SerializeSmi(Tagged<Smi> object);
void SerializeDouble(double number);
template <bool no_escaping>
FastJsonStringifierObjectKeyResult SerializeObjectKey(
Tagged<String> key, bool comma, const DisallowGarbageCollection& no_gc);
template <typename StringT, bool no_escaping>
FastJsonStringifierObjectKeyResult SerializeObjectKey(
Tagged<String> key, bool comma, const DisallowGarbageCollection& no_gc);
template <typename StringT>
V8_INLINE FastJsonStringifierResult SerializeString(
Tagged<HeapObject> str, const DisallowGarbageCollection& no_gc);
FastJsonStringifierResult TrySerializeSimpleObject(Tagged<JSAny> object);
FastJsonStringifierResult SerializeObject(
ContinuationRecord cont, const DisallowGarbageCollection& no_gc);
V8_NOINLINE FastJsonStringifierResult SerializeJSPrimitiveWrapper(
Tagged<JSPrimitiveWrapper> obj, const DisallowGarbageCollection& no_gc);
V8_INLINE FastJsonStringifierResult SerializeJSObject(
Tagged<JSObject> obj, const DisallowGarbageCollection& no_gc);
template <DescriptorArray::FastIterableState fast_iterable_state>
V8_INLINE FastJsonStringifierResult ResumeJSObject(
Tagged<JSObject> obj, uint16_t start_descriptor_idx,
uint16_t nof_descriptors, uint8_t in_object_properties,
uint8_t in_object_properties_start, Tagged<DescriptorArray> descriptors,
bool comma, const DisallowGarbageCollection& no_gc);
FastJsonStringifierResult SerializeJSArray(Tagged<JSArray> array);
template <ElementsKind kind>
FastJsonStringifierResult SerializeFixedArrayWithInterruptCheck(
Tagged<FixedArrayBase> elements, uint32_t start_index, uint32_t length);
template <ElementsKind kind>
V8_INLINE FastJsonStringifierResult SerializeFixedArray(
Tagged<FixedArrayBase> array, uint32_t start_idx, uint32_t length);
template <ElementsKind kind, bool with_interrupt_checks, typename T>
V8_INLINE FastJsonStringifierResult
SerializeFixedArrayElement(Tagged<T> elements, uint32_t i, uint32_t length);
V8_NOINLINE FastJsonStringifierResult HandleInterruptAndCheckCycle();
V8_NOINLINE bool CheckCycle();
V8_INLINE void EnsureCapacity(size_t size) { buffer_.EnsureCapacity(size); }
template <typename SrcChar>
V8_INLINE void AppendCharacterUnchecked(SrcChar c) {
buffer_.AppendCharacter(c);
}
template <typename SrcChar>
V8_INLINE void AppendCharacter(SrcChar c) {
EnsureCapacity(1);
AppendCharacterUnchecked(c);
}
template <size_t N>
V8_INLINE void AppendCStringLiteralUnchecked(const char (&literal)[N]) {
// Note that the literal contains the zero char.
constexpr size_t length = N - 1;
static_assert(length > 0);
if constexpr (length == 1) return AppendCharacterUnchecked(literal[0]);
const uint8_t* chars = reinterpret_cast<const uint8_t*>(literal);
buffer_.Append(chars, length);
}
template <size_t N>
V8_INLINE void AppendCStringLiteral(const char (&literal)[N]) {
// Note that the literal contains the zero char.
constexpr size_t length = N - 1;
static_assert(length > 0);
EnsureCapacity(length);
AppendCStringLiteralUnchecked(literal);
}
V8_INLINE void AppendCStringUnchecked(const char* chars, size_t len) {
buffer_.Append(reinterpret_cast<const unsigned char*>(chars), len);
}
V8_INLINE void AppendCStringUnchecked(const char* chars) {
AppendCStringUnchecked(chars, strlen(chars));
}
V8_INLINE void AppendStringUnchecked(std::string_view str) {
AppendCStringUnchecked(str.data(), str.length());
}
V8_INLINE void AppendCString(const char* chars, size_t len) {
EnsureCapacity(len);
AppendCStringUnchecked(chars, len);
}
V8_INLINE void AppendCString(const char* chars) {
AppendCString(chars, strlen(chars));
}
V8_INLINE void AppendString(std::string_view str) {
AppendCString(str.data(), str.length());
}
template <typename SrcChar>
requires(sizeof(SrcChar) == sizeof(uint8_t))
V8_INLINE bool AppendString(const SrcChar* chars, size_t length,
const DisallowGarbageCollection& no_gc);
template <typename SrcChar>
V8_INLINE void AppendStringNoEscapes(const SrcChar* chars, size_t length,
const DisallowGarbageCollection& no_gc);
template <typename SrcChar>
requires(sizeof(SrcChar) == sizeof(uint8_t))
bool AppendStringScalar(const SrcChar* chars, size_t length, size_t start,
size_t uncopied_src_index,
const DisallowGarbageCollection& no_gc);
template <typename SrcChar>
requires(sizeof(SrcChar) == sizeof(uint8_t))
V8_INLINE bool AppendStringSWAR(const SrcChar* chars, size_t length,
size_t start, size_t uncopied_src_index,
const DisallowGarbageCollection& no_gc);
template <typename SrcChar>
requires(sizeof(SrcChar) == sizeof(uint8_t))
V8_INLINE bool AppendStringSIMD(const SrcChar* chars, size_t length,
const DisallowGarbageCollection& no_gc);
template <typename SrcChar>
requires(sizeof(SrcChar) == sizeof(base::uc16))
V8_INLINE bool AppendString(const SrcChar* chars, size_t length,
const DisallowGarbageCollection& no_gc);
using FastIterableState = DescriptorArray::FastIterableState;
static constexpr uint32_t kGlobalInterruptBudget = 200000;
static constexpr uint32_t kArrayInterruptLength = 4000;
Isolate* isolate_;
OutBuffer<Char> buffer_;
base::SmallVector<ContinuationRecord, 16> stack_;
Tagged<HeapObject> initial_jsobject_proto_;
Tagged<HeapObject> initial_jsarray_proto_;
template <typename>
friend class FastJsonStringifier;
};
namespace {
V8_INLINE bool CanFastSerializeJSArrayFastPath(Tagged<JSArray> object,
Tagged<HeapObject> initial_proto,
Isolate* isolate) {
// Check if the prototype is the initial array prototype without interesting
// properties (toJSON).
Tagged<Map> map = object->map();
if (V8_UNLIKELY(map->may_have_interesting_properties())) return false;
Tagged<HeapObject> proto = map->prototype();
// Note: This will also fail for sub-objects in different native contexts.
// This should be rare and bailing-out to the slow-path is fine.
return V8_LIKELY(proto == initial_proto);
}
V8_INLINE bool CanFastSerializeJSObjectFastPath(
Tagged<JSObject> object, Tagged<HeapObject> initial_proto, Tagged<Map> map,
Isolate* isolate) {
if (V8_UNLIKELY(IsCustomElementsReceiverMap(map))) return false;
if (V8_UNLIKELY(!object->HasFastProperties())) return false;
auto roots = ReadOnlyRoots(isolate);
auto elements = object->elements();
if (V8_UNLIKELY(elements != roots.empty_fixed_array() &&
elements != roots.empty_slow_element_dictionary())) {
return false;
}
if (V8_UNLIKELY(map->may_have_interesting_properties())) return false;
Tagged<HeapObject> proto = map->prototype();
// Note: This will also fail for sub-objects in different native contexts.
// This should be rare and bailing-out to the slow-path is fine.
return V8_LIKELY(proto == initial_proto);
}
} // namespace
template <typename Char>
FastJsonStringifier<Char>::FastJsonStringifier(Isolate* isolate)
: isolate_(isolate), buffer_(isolate->allocator()) {}
template <typename Char>
void FastJsonStringifier<Char>::SerializeSmi(Tagged<Smi> object) {
static_assert(Smi::kMaxValue <= 2147483647);
static_assert(Smi::kMinValue >= -2147483648);
// sizeof(string) includes \0.
static constexpr uint32_t kBufferSize = sizeof("-2147483648") - 1;
char chars[kBufferSize];
base::Vector<char> buffer(chars, kBufferSize);
std::string_view str = IntToStringView(object.value(), buffer);
AppendString(str);
}
template <typename Char>
void FastJsonStringifier<Char>::SerializeDouble(double number) {
if (V8_UNLIKELY(std::isinf(number) || std::isnan(number))) {
AppendCStringLiteral("null");
return;
}
static constexpr uint32_t kBufferSize = 100;
char chars[kBufferSize];
base::Vector<char> buffer(chars, kBufferSize);
std::string_view str = DoubleToStringView(number, buffer);
AppendString(str);
}
template <typename Char>
template <bool no_escaping>
FastJsonStringifierObjectKeyResult
FastJsonStringifier<Char>::SerializeObjectKey(
Tagged<String> key, bool comma, const DisallowGarbageCollection& no_gc) {
#if V8_STATIC_ROOTS_BOOL
// This is slightly faster than the switch over instance types.
ReadOnlyRoots roots(isolate_);
Tagged<Map> map = key->map();
if (map == roots.internalized_one_byte_string_map()) {
V8_INLINE_STATEMENT return SerializeObjectKey<SeqOneByteString,
no_escaping>(key, comma,
no_gc);
} else if (map == roots.external_internalized_one_byte_string_map() ||
map ==
roots.uncached_external_internalized_one_byte_string_map()) {
V8_INLINE_STATEMENT return SerializeObjectKey<ExternalOneByteString,
no_escaping>(key, comma,
no_gc);
} else {
if constexpr (is_one_byte) {
DCHECK(InstanceTypeChecker::IsTwoByteString(map));
// no_escaping implies that all keys are one-byte.
if constexpr (no_escaping) {
UNREACHABLE();
} else {
return FastJsonStringifierObjectKeyResult::kChangeEncoding;
}
} else {
if (map == roots.internalized_two_byte_string_map()) {
return SerializeObjectKey<SeqTwoByteString, no_escaping>(key, comma,
no_gc);
} else if (
map == roots.external_internalized_two_byte_string_map() ||
map == roots.uncached_external_internalized_two_byte_string_map()) {
return SerializeObjectKey<ExternalTwoByteString, no_escaping>(
key, comma, no_gc);
}
}
}
#else
InstanceType instance_type = key->map()->instance_type();
switch (instance_type) {
case INTERNALIZED_ONE_BYTE_STRING_TYPE:
V8_INLINE_STATEMENT return SerializeObjectKey<SeqOneByteString,
no_escaping>(key, comma,
no_gc);
case EXTERNAL_INTERNALIZED_ONE_BYTE_STRING_TYPE:
case UNCACHED_EXTERNAL_INTERNALIZED_ONE_BYTE_STRING_TYPE:
V8_INLINE_STATEMENT return SerializeObjectKey<ExternalOneByteString,
no_escaping>(key, comma,
no_gc);
case INTERNALIZED_TWO_BYTE_STRING_TYPE:
return SerializeObjectKey<SeqTwoByteString, no_escaping>(key, comma,
no_gc);
case EXTERNAL_INTERNALIZED_TWO_BYTE_STRING_TYPE:
case UNCACHED_EXTERNAL_INTERNALIZED_TWO_BYTE_STRING_TYPE:
return SerializeObjectKey<ExternalTwoByteString, no_escaping>(key, comma,
no_gc);
default:
UNREACHABLE();
}
#endif
UNREACHABLE();
}
namespace {
// The worst case length of an escaped character is 6. Shifting the string
// length left by 3 is a more pessimistic estimate, but faster to calculate.
size_t MaxEscapedStringLength(size_t length) { return length << 3; }
} // namespace
template <typename Char>
template <typename StringT, bool no_escaping>
FastJsonStringifierObjectKeyResult
FastJsonStringifier<Char>::SerializeObjectKey(
Tagged<String> obj, bool comma, const DisallowGarbageCollection& no_gc) {
using StringChar = StringT::Char;
if constexpr (is_one_byte && sizeof(StringChar) == 2) {
// no_escaping is only possible if we have already seen all the keys in a
// map. But it is not possible we have seen a two-byte string and are still
// in one-byte mode.
if constexpr (no_escaping) {
UNREACHABLE();
} else {
return FastJsonStringifierObjectKeyResult::kChangeEncoding;
}
} else {
Tagged<StringT> string = Cast<StringT>(obj);
const StringChar* chars;
if constexpr (requires { string->GetChars(no_gc); }) {
chars = string->GetChars(no_gc);
} else {
chars = string->GetChars();
}
const uint32_t length = string->length();
size_t max_length;
if constexpr (no_escaping) {
max_length = length;
} else {
max_length = MaxEscapedStringLength(length);
}
max_length += 4 /* optional comma + 2x double quote + colon */;
EnsureCapacity(max_length);
SeparatorUnchecked(comma);
AppendCharacterUnchecked('"');
FastJsonStringifierObjectKeyResult result;
if constexpr (no_escaping) {
DCHECK(IsFastKey(obj, no_gc));
SLOW_DCHECK(String::DoesNotContainEscapeCharacters(obj));
AppendStringNoEscapes(chars, length, no_gc);
result = FastJsonStringifierObjectKeyResult::kSuccess;
} else {
bool needs_escaping = AppendString(chars, length, no_gc);
DCHECK_IMPLIES(needs_escaping, !IsFastKey(obj, no_gc));
SLOW_DCHECK(needs_escaping !=
String::DoesNotContainEscapeCharacters(obj));
result = sizeof(StringChar) == 1 && !needs_escaping
? FastJsonStringifierObjectKeyResult::kSuccess
: FastJsonStringifierObjectKeyResult::kSlow;
}
AppendCharacterUnchecked('"');
AppendCharacterUnchecked(':');
return result;
}
}
template <typename Char>
template <typename StringT>
FastJsonStringifierResult FastJsonStringifier<Char>::SerializeString(
Tagged<HeapObject> obj, const DisallowGarbageCollection& no_gc) {
using StringChar = StringT::Char;
if constexpr (is_one_byte && sizeof(StringChar) == 2) {
return CHANGE_ENCODING;
} else {
Tagged<StringT> string = Cast<StringT>(obj);
const StringChar* chars;
if constexpr (requires { string->GetChars(no_gc); }) {
chars = string->GetChars(no_gc);
} else {
chars = string->GetChars();
}
const uint32_t length = string->length();
EnsureCapacity(MaxEscapedStringLength(length) + 2 /* 2x double quote */);
AppendCharacterUnchecked('"');
AppendString(chars, length, no_gc);
AppendCharacterUnchecked('"');
return SUCCESS;
}
}
template <typename Char>
FastJsonStringifierResult FastJsonStringifier<Char>::TrySerializeSimpleObject(
Tagged<JSAny> object) {
DisallowGarbageCollection no_gc;
// GCMole doesn't handle kNoGC interrupts correctly.
DisableGCMole no_gc_mole;
if (IsSmi(object)) {
SerializeSmi(Cast<Smi>(object));
return SUCCESS;
}
Tagged<HeapObject> obj = Cast<HeapObject>(object);
Tagged<Map> map = obj->map();
InstanceType instance_type = map->instance_type();
switch (instance_type) {
case INTERNALIZED_ONE_BYTE_STRING_TYPE:
case SEQ_ONE_BYTE_STRING_TYPE:
return SerializeString<SeqOneByteString>(obj, no_gc);
case EXTERNAL_INTERNALIZED_ONE_BYTE_STRING_TYPE:
case UNCACHED_EXTERNAL_INTERNALIZED_ONE_BYTE_STRING_TYPE:
case EXTERNAL_ONE_BYTE_STRING_TYPE:
case UNCACHED_EXTERNAL_ONE_BYTE_STRING_TYPE:
return SerializeString<ExternalOneByteString>(obj, no_gc);
case THIN_ONE_BYTE_STRING_TYPE: {
Tagged<String> actual = Cast<ThinString>(obj)->actual();
if (IsExternalString(actual)) {
return SerializeString<ExternalOneByteString>(actual, no_gc);
} else {
return SerializeString<SeqOneByteString>(actual, no_gc);
}
}
case INTERNALIZED_TWO_BYTE_STRING_TYPE:
case SEQ_TWO_BYTE_STRING_TYPE:
return SerializeString<SeqTwoByteString>(obj, no_gc);
case EXTERNAL_INTERNALIZED_TWO_BYTE_STRING_TYPE:
case UNCACHED_EXTERNAL_INTERNALIZED_TWO_BYTE_STRING_TYPE:
case EXTERNAL_TWO_BYTE_STRING_TYPE:
case UNCACHED_EXTERNAL_TWO_BYTE_STRING_TYPE:
return SerializeString<ExternalTwoByteString>(obj, no_gc);
case THIN_TWO_BYTE_STRING_TYPE: {
if constexpr (is_one_byte) {
return CHANGE_ENCODING;
} else {
Tagged<String> actual = Cast<ThinString>(obj)->actual();
if (IsExternalString(actual)) {
return SerializeString<ExternalTwoByteString>(actual, no_gc);
} else {
return SerializeString<SeqTwoByteString>(actual, no_gc);
}
}
}
case HEAP_NUMBER_TYPE:
SerializeDouble(Cast<HeapNumber>(obj)->value());
return SUCCESS;
case ODDBALL_TYPE:
switch (Cast<Oddball>(obj)->kind()) {
case Oddball::kFalse:
AppendCStringLiteral("false");
return SUCCESS;
case Oddball::kTrue:
AppendCStringLiteral("true");
return SUCCESS;
case Oddball::kNull:
AppendCStringLiteral("null");
return SUCCESS;
default:
return UNDEFINED;
}
case SYMBOL_TYPE:
return UNDEFINED;
case JS_PRIMITIVE_WRAPPER_TYPE:
return SerializeJSPrimitiveWrapper(Cast<JSPrimitiveWrapper>(obj), no_gc);
case JS_OBJECT_TYPE:
return JS_OBJECT;
case JS_ARRAY_TYPE:
return JS_ARRAY;
default:
return SLOW_PATH;
}
UNREACHABLE();
}
namespace {
Builtin GetBuiltin(Isolate* isolate, Tagged<JSObject> obj,
DirectHandle<Name> name) {
HandleScope handle_scope(isolate);
DirectHandle<JSObject> obj_handle = direct_handle(obj, isolate);
LookupIterator it(isolate, obj_handle, name, obj_handle);
if (!it.IsFound()) {
return Builtin::kNoBuiltinId;
}
if (it.state() != LookupIterator::DATA) {
return Builtin::kNoBuiltinId;
}
DirectHandle<Object> fun = Object::GetProperty(&it).ToHandleChecked();
if (!IsJSFunction(*fun)) {
return Builtin::kNoBuiltinId;
}
return Cast<JSFunction>(*fun)->code(isolate)->builtin_id();
}
} // namespace
template <typename Char>
FastJsonStringifierResult
FastJsonStringifier<Char>::SerializeJSPrimitiveWrapper(
Tagged<JSPrimitiveWrapper> obj, const DisallowGarbageCollection& no_gc) {
// TODO(pthier): Consider extending IsStringWrapperToPrimitive to also cover
// toString() and all JSPrimitiveWrappers to avoid some of the checks here.
if (V8_UNLIKELY(MayHaveInterestingProperties(isolate_, obj))) {
return SLOW_PATH;
}
Tagged<JSAny> raw = obj->value();
if (IsSymbol(raw)) {
// Symbol object wrapper is treated as a regular object.
Tagged<Map> map = obj->map();
// Check that object has no own properties.
if (V8_UNLIKELY(!obj->HasFastProperties())) return SLOW_PATH;
if (V8_UNLIKELY(map->NumberOfOwnDescriptors() != 0)) return SLOW_PATH;
// Check that object has no elements.
auto roots = ReadOnlyRoots(isolate_);
auto elements = obj->elements();
if (V8_UNLIKELY(elements != roots.empty_fixed_array() &&
elements != roots.empty_slow_element_dictionary())) {
return SLOW_PATH;
}
AppendCStringLiteral("{}");
return SUCCESS;
} else if (IsString(raw)) {
if (V8_UNLIKELY(!Protectors::IsStringWrapperToPrimitiveIntact(isolate_))) {
return SLOW_PATH;
}
// We only fetch data properties in GetBuiltin, but GCMole doesn't know
// that and assumes GCs can happen.
DisableGCMole no_gc_mole;
// Check that toString() on the prototype chain is the expected builtin.
if (V8_UNLIKELY(
GetBuiltin(isolate_, obj, isolate_->factory()->toString_string()) !=
Builtin::kStringPrototypeToString)) {
return SLOW_PATH;
}
Tagged<String> string = Cast<String>(raw);
while (true) {
FastJsonStringifierResult result =
string->DispatchToSpecificType(absl::Overload{
[&](Tagged<SeqOneByteString> str) {
return SerializeString<SeqOneByteString>(str, no_gc);
},
[&](Tagged<SeqTwoByteString> str) {
return SerializeString<SeqTwoByteString>(str, no_gc);
},
[&](Tagged<ExternalOneByteString> str) {
return SerializeString<ExternalOneByteString>(str, no_gc);
},
[&](Tagged<ExternalTwoByteString> str) {
return SerializeString<ExternalTwoByteString>(str, no_gc);
},
[&](Tagged<ThinString> str) {
string = str->actual();
// UNDEFINED is misused here to indicate that we continue after
// unwrapping.
return UNDEFINED;
},
[&](Tagged<ConsString> str) { return SLOW_PATH; },
[&](Tagged<SlicedString> str) { return SLOW_PATH; }});
if (V8_LIKELY(result != UNDEFINED)) return result;
}
UNREACHABLE();
} else if (IsNumber(raw)) {
// We only fetch data properties in GetBuiltin, but GCMole doesn't know
// that and assumes GCs can happen.
DisableGCMole no_gc_mole;
// Check that valueOf() on the prototype chain is the expected builtin.
if (V8_UNLIKELY(
GetBuiltin(isolate_, obj, isolate_->factory()->valueOf_string()) !=
Builtin::kNumberPrototypeValueOf)) {
return SLOW_PATH;
}
if (IsSmi(raw)) {
SerializeSmi(Cast<Smi>(raw));
return SUCCESS;
}
DCHECK(IsHeapNumber(raw));
SerializeDouble(Cast<HeapNumber>(raw)->value());
return SUCCESS;
} else if (IsBoolean(raw)) {
if (IsTrue(raw, isolate_)) {
AppendCStringLiteral("true");
} else {
AppendCStringLiteral("false");
}
return SUCCESS;
} else if (IsBigInt(raw)) {
return SLOW_PATH;
}
UNREACHABLE();
}
template <typename Char>
FastJsonStringifierResult FastJsonStringifier<Char>::SerializeJSObject(
Tagged<JSObject> obj, const DisallowGarbageCollection& no_gc) {
Tagged<Map> map = obj->map();
if (V8_UNLIKELY(!CanFastSerializeJSObjectFastPath(
obj, initial_jsobject_proto_, map, isolate_))) {
return SLOW_PATH;
}
AppendCharacter('{');
const uint16_t nof_descriptors = map->NumberOfOwnDescriptors();
const uint8_t in_object_properties = map->GetInObjectProperties();
const uint8_t in_object_properties_start =
map->GetInObjectPropertiesStartInWords();
const Tagged<DescriptorArray> descriptors = map->instance_descriptors();
FastIterableState fast_iterable_state = descriptors->fast_iterable();
switch (fast_iterable_state) {
#define CASE(state) \
case FastIterableState::state: \
return ResumeJSObject<FastIterableState::state>( \
obj, 0, nof_descriptors, in_object_properties, \
in_object_properties_start, descriptors, false, no_gc)
CASE(kUnknown);
CASE(kJsonFast);
CASE(kJsonSlow);
#undef CASE
}
UNREACHABLE();
}
template <typename Char>
template <DescriptorArray::FastIterableState fast_iterable_state>
FastJsonStringifierResult FastJsonStringifier<Char>::ResumeJSObject(
Tagged<JSObject> obj, uint16_t start_descriptor_idx,
uint16_t nof_descriptors, uint8_t in_object_properties,
uint8_t in_object_properties_start, Tagged<DescriptorArray> descriptors,
bool comma, const DisallowGarbageCollection& no_gc) {
PtrComprCageBase cage_base = GetPtrComprCageBase();
InternalIndex::Range range{start_descriptor_idx, nof_descriptors};
for (InternalIndex i : range) {
// We don't deal with dictionary mode objects on the fast-path, so the index
// is guaranteed to be less than uint16_t::max.
static_assert(kMaxNumberOfDescriptors <
std::numeric_limits<uint16_t>::max());
DCHECK_LE(i.as_uint32(), kMaxNumberOfDescriptors);
const uint16_t descriptor_idx = static_cast<uint16_t>(i.as_uint32());
Tagged<Name> name = descriptors->GetKey(i);
int property_index;
if constexpr (fast_iterable_state != FastIterableState::kJsonFast) {
if (V8_UNLIKELY(IsSymbol(name))) {
if constexpr (fast_iterable_state == FastIterableState::kUnknown) {
descriptors->set_fast_iterable(FastIterableState::kJsonSlow);
}
continue;
}
PropertyDetails details = descriptors->GetDetails(i);
if (V8_UNLIKELY(details.IsDontEnum())) {
if constexpr (fast_iterable_state == FastIterableState::kUnknown) {
descriptors->set_fast_iterable(FastIterableState::kJsonSlow);
}
continue;
}
if (V8_UNLIKELY(details.location() != PropertyLocation::kField)) {
descriptors->set_fast_iterable(FastIterableState::kJsonSlow);
return SLOW_PATH;
}
DCHECK_EQ(PropertyKind::kData, details.kind());
property_index = details.field_index();
} else {
DCHECK_EQ(descriptor_idx, descriptors->GetDetails(i).field_index());
property_index = descriptor_idx;
}
const bool is_inobject = property_index < in_object_properties;
Tagged<JSAny> property;
if (is_inobject) {
int offset = (in_object_properties_start + property_index) * kTaggedSize;
property = TaggedField<JSAny>::Relaxed_Load(cage_base, obj, offset);
} else {
property_index -= in_object_properties;
property = obj->property_array(cage_base)->get(cage_base, property_index);
}
DCHECK(IsInternalizedString(name));
Tagged<String> key_name = Cast<String>(name);
if (V8_UNLIKELY(IsUndefined(property) || IsSymbol(property))) {
if constexpr (fast_iterable_state == FastIterableState::kUnknown) {
// Even if we don't serialize the key, check if it is fast iterable to
// avoid false positives in DescriptorArray::fast_iterable.
if (!IsFastKey(key_name, no_gc)) {
descriptors->set_fast_iterable(FastIterableState::kJsonSlow);
}
}
continue;
}
FastJsonStringifierObjectKeyResult key_result;
if constexpr (fast_iterable_state == FastIterableState::kJsonFast) {
V8_INLINE_STATEMENT key_result =
SerializeObjectKey<true>(key_name, comma, no_gc);
DCHECK_EQ(key_result, FastJsonStringifierObjectKeyResult::kSuccess);
} else {
key_result = SerializeObjectKey<false>(key_name, comma, no_gc);
if (V8_UNLIKELY(key_result !=
FastJsonStringifierObjectKeyResult::kSuccess)) {
descriptors->set_fast_iterable(FastIterableState::kJsonSlow);
if constexpr (is_one_byte) {
if (key_result ==
FastJsonStringifierObjectKeyResult::kChangeEncoding) {
stack_.emplace_back(
ContinuationRecord::ForJSObjectResume<fast_iterable_state>(
obj, descriptor_idx + 1, nof_descriptors,
in_object_properties, in_object_properties_start,
descriptors));
if (IsJSObject(property)) {
stack_.emplace_back(ContinuationRecord::ForJSObject(property));
} else if (IsJSArray(property)) {
stack_.emplace_back(ContinuationRecord::ForJSArray(property));
} else {
stack_.emplace_back(
ContinuationRecord::ForSimpleObject(property));
}
stack_.emplace_back(
ContinuationRecord::ForObjectKey(key_name, comma));
return CHANGE_ENCODING;
}
} else {
DCHECK_NE(key_result,
FastJsonStringifierObjectKeyResult::kChangeEncoding);
}
}
}
// TrySerializeSimpleObject won't trigger GCs. See DisableGCMole scopes in
// SerializeJSPrimitiveWrapper for explanation.
DisableGCMole no_gc_mole;
FastJsonStringifierResult result;
if constexpr (fast_iterable_state == FastIterableState::kJsonFast) {
V8_INLINE_STATEMENT result = TrySerializeSimpleObject(property);
} else {
result = TrySerializeSimpleObject(property);
}
switch (result) {
case SUCCESS:
comma = true;
break;
case UNDEFINED:
break;
case JS_OBJECT:
case JS_ARRAY:
stack_.push_back(
ContinuationRecord::ForJSObjectResume<fast_iterable_state>(
obj, descriptor_idx + 1, nof_descriptors, in_object_properties,
in_object_properties_start, descriptors));
stack_.push_back(ContinuationRecord::ForJSAny(property, result));
return result;
case CHANGE_ENCODING:
if constexpr (is_one_byte) {
stack_.push_back(
ContinuationRecord::ForJSObjectResume<fast_iterable_state>(
obj, descriptor_idx + 1, nof_descriptors,
in_object_properties, in_object_properties_start,
descriptors));
stack_.push_back(ContinuationRecord::ForSimpleObject(property));
return result;
} else {
UNREACHABLE();
}
case SLOW_PATH:
case EXCEPTION:
return result;
}
}
AppendCharacter('}');
if constexpr (fast_iterable_state == FastIterableState::kUnknown) {
if (nof_descriptors == descriptors->number_of_descriptors()) {
descriptors->set_fast_iterable_if(FastIterableState::kJsonFast,
FastIterableState::kUnknown);
}
}
return SUCCESS;
}
template <typename Char>
FastJsonStringifierResult FastJsonStringifier<Char>::SerializeJSArray(
Tagged<JSArray> array) {
if (V8_UNLIKELY(!CanFastSerializeJSArrayFastPath(
array, initial_jsarray_proto_, isolate_))) {
return SLOW_PATH;
}
AppendCharacter('[');
uint32_t length = static_cast<uint32_t>(Object::NumberValue(array->length()));
Tagged<FixedArrayBase> elements = array->elements();
switch (array->GetElementsKind()) {
#define CASE(kind) \
case kind: \
if constexpr (IsHoleyElementsKind(kind)) { \
if (V8_UNLIKELY(!Protectors::IsNoElementsIntact(isolate_))) { \
return SLOW_PATH; \
} \
} \
if (V8_UNLIKELY(length > kArrayInterruptLength)) { \
return SerializeFixedArrayWithInterruptCheck<kind>(elements, 0, length); \
} else { \
return SerializeFixedArray<kind>(elements, 0, length); \
}
CASE(PACKED_SMI_ELEMENTS)
CASE(PACKED_ELEMENTS)
CASE(PACKED_DOUBLE_ELEMENTS)
CASE(HOLEY_SMI_ELEMENTS)
CASE(HOLEY_ELEMENTS)
CASE(HOLEY_DOUBLE_ELEMENTS)
#undef CASE
default:
return SLOW_PATH;
}
UNREACHABLE();
}
template <typename Char>
template <ElementsKind kind>
FastJsonStringifierResult
FastJsonStringifier<Char>::SerializeFixedArrayWithInterruptCheck(
Tagged<FixedArrayBase> elements, uint32_t start_index, uint32_t length) {
using ArrayT = std::conditional_t<IsDoubleElementsKind(kind),
FixedDoubleArray, FixedArray>;
StackLimitCheck interrupt_check(isolate_);
uint32_t limit = std::min(length, kArrayInterruptLength);
constexpr uint32_t kMaxAllowedFastPackedLength =
std::numeric_limits<uint32_t>::max() - kArrayInterruptLength;
static_assert(FixedArray::kMaxLength < kMaxAllowedFastPackedLength);
// GCMole doesn't handle kNoGC interrupts correctly.
DisableGCMole no_gc_mole;
uint32_t i = start_index;
while (true) {
for (; i < limit; i++) {
FastJsonStringifierResult result = SerializeFixedArrayElement<kind, true>(
Cast<ArrayT>(elements), i, length);
if (result != SUCCESS) return result;
}
if (i >= length) {
AppendCharacter(']');
return SUCCESS;
}
DCHECK_LT(limit, kMaxAllowedFastPackedLength);
limit = std::min(length, limit + kArrayInterruptLength);
{
// A TerminationException can actually trigger a GC when allocating the
// message object. We don't touch any of the heap objects after we
// encountered an exception, so this is fine.
AllowGarbageCollection allow_gc;
if (interrupt_check.InterruptRequested() &&
IsException(isolate_->stack_guard()->HandleInterrupts(
StackGuard::InterruptLevel::kNoGC),
isolate_)) {
return EXCEPTION;
}
}
}
UNREACHABLE();
}
template <typename Char>
template <ElementsKind kind>
FastJsonStringifierResult FastJsonStringifier<Char>::SerializeFixedArray(
Tagged<FixedArrayBase> elements, uint32_t start_index, uint32_t length) {
using ArrayT = std::conditional_t<IsDoubleElementsKind(kind),
FixedDoubleArray, FixedArray>;
for (uint32_t i = start_index; i < length; i++) {
FastJsonStringifierResult result = SerializeFixedArrayElement<kind, false>(
Cast<ArrayT>(elements), i, length);
if (result != SUCCESS) return result;
}
AppendCharacter(']');
return SUCCESS;
}
template <typename Char>
template <ElementsKind kind, bool with_interrupt_checks, typename T>
FastJsonStringifierResult FastJsonStringifier<Char>::SerializeFixedArrayElement(
Tagged<T> elements, uint32_t i, uint32_t length) {
if constexpr (IsHoleyElementsKind(kind)) {
if (elements->is_the_hole(isolate_, i)) {
EnsureCapacity(5 /* "null" + optional comma */);
SeparatorUnchecked(i > 0);
AppendCStringLiteralUnchecked("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 {
Tagged<JSAny> obj = Cast<JSAny>(elements->get(i));
// TrySerializeSimpleObject won't trigger GCs. See DisableGCMole scopes in
// SerializeJSPrimitiveWrapper for explanation.
DisableGCMole no_gc_mole;
FastJsonStringifierResult result;
V8_INLINE_STATEMENT result = TrySerializeSimpleObject(obj);
switch (result) {
case UNDEFINED:
AppendCStringLiteral("null");
return SUCCESS;
case CHANGE_ENCODING:
if constexpr (is_one_byte) {
DCHECK(IsString(obj) || IsStringWrapper(obj));
stack_.push_back(
ContinuationRecord::ForJSArrayResume<kind, with_interrupt_checks>(
elements, i + 1, length));
stack_.push_back(ContinuationRecord::ForSimpleObject(obj));
return result;
} else {
UNREACHABLE();
}
case JS_OBJECT:
case JS_ARRAY:
stack_.push_back(
ContinuationRecord::ForJSArrayResume<kind, with_interrupt_checks>(
elements, i + 1, length));
stack_.push_back(ContinuationRecord::ForJSAny(obj, result));
return result;
case SUCCESS:
case SLOW_PATH:
case EXCEPTION:
return result;
}
UNREACHABLE();
}
return SUCCESS;
}
template <typename Char>
template <typename OldChar>
requires(sizeof(OldChar) < sizeof(Char))
FastJsonStringifierResult FastJsonStringifier<Char>::ResumeFrom(
FastJsonStringifier<OldChar>& old_stringifier,
const DisallowGarbageCollection& no_gc) {
DCHECK_EQ(ResultLength(), 0);
DCHECK(stack_.empty());
DCHECK(!old_stringifier.stack_.empty());
initial_jsobject_proto_ = old_stringifier.initial_jsobject_proto_;
initial_jsarray_proto_ = old_stringifier.initial_jsarray_proto_;
stack_ = old_stringifier.stack_;
ContinuationRecord cont = stack_.back();
stack_.pop_back();
if (cont.type() == ContinuationRecord::kObjectKey) {
// Serializing an object key caused an encoding change.
FastJsonStringifierObjectKeyResult key_result = SerializeObjectKey<false>(
cont.object_key(), cont.object_key_comma(), no_gc);
USE(key_result);
DCHECK_NE(key_result, FastJsonStringifierObjectKeyResult::kChangeEncoding);
// Resuming due to encoding change of an object key guarantees that there
// are at least two other objects on the stack (the value for that key, and
// the continuation record for the object the key is a member of).
DCHECK_GE(stack_.size(), 2);
cont = stack_.back();
stack_.pop_back();
// Check that we have the object's continuation record.
DCHECK(ContinuationRecord::IsObjectResumeType(stack_.back().type()));
}
if (cont.type() == ContinuationRecord::kSimpleObject) {
// TrySerializeSimpleObject won't trigger GCs. See DisableGCMole scopes in
// SerializeJSPrimitiveWrapper for explanation.
DisableGCMole no_gc_mole;
FastJsonStringifierResult result =
TrySerializeSimpleObject(cont.simple_object());
if (V8_UNLIKELY(result != SUCCESS)) {
DCHECK_EQ(result, SLOW_PATH);
return result;
}
// Early return if a top-level string triggered the encoding change.
if (stack_.empty()) return result;
cont = stack_.back();
stack_.pop_back();
}
DCHECK(cont.type() != ContinuationRecord::kSimpleObject &&
cont.type() != ContinuationRecord::kObjectKey);
return SerializeObject(cont, no_gc);
}
template <typename Char>
FastJsonStringifierResult FastJsonStringifier<Char>::SerializeObject(
Tagged<JSAny> object, const DisallowGarbageCollection& no_gc) {
// Initial checks if using the fast-path is possible in general.
if (!object.IsSmi() && (IsJSObject(object) || IsJSArray(object))) {
Tagged<HeapObject> obj = Cast<HeapObject>(object);
// Load initial JSObject/JSArray prototypes from native context.
Tagged<Map> meta_map = obj->map()->map();
// Don't deal with context-less meta maps.
if (V8_UNLIKELY(meta_map == ReadOnlyRoots(isolate_).meta_map())) {
return SLOW_PATH;
}
Tagged<NativeContext> native_context = meta_map->native_context();
initial_jsobject_proto_ = native_context->initial_object_prototype();
initial_jsarray_proto_ = native_context->initial_array_prototype();
// Prototypes don't have interesting properties (toJSON).
if (V8_UNLIKELY(
initial_jsarray_proto_->map()->may_have_interesting_properties())) {
return SLOW_PATH;
}
if (V8_UNLIKELY(initial_jsobject_proto_->map()
->may_have_interesting_properties())) {
return SLOW_PATH;
}
// JSArray's prototype is the initial object prototype.
Tagged<HeapObject> jsarray_proto_proto =
initial_jsarray_proto_->map()->prototype();
if (V8_UNLIKELY(jsarray_proto_proto != initial_jsobject_proto_)) {
return SLOW_PATH;
}
}
// TrySerializeSimpleObject won't trigger GCs. See DisableGCMole scopes in
// SerializeJSPrimitiveWrapper for explanation.
DisableGCMole no_gc_mole;
// Serialize object.
FastJsonStringifierResult result = TrySerializeSimpleObject(object);
if constexpr (is_one_byte) {
if (V8_UNLIKELY(result == CHANGE_ENCODING)) {
DCHECK(IsString(object) || IsStringWrapper(object));
stack_.push_back(ContinuationRecord::ForSimpleObject(object));
return result;
}
} else {
DCHECK_NE(result, CHANGE_ENCODING);
}
if (result != JS_OBJECT && result != JS_ARRAY) {
return result;
}
return SerializeObject(ContinuationRecord::ForJSAny(object, result), no_gc);
}
template <typename Char>
FastJsonStringifierResult FastJsonStringifier<Char>::SerializeObject(
ContinuationRecord cont, const DisallowGarbageCollection& no_gc) {
// GCMole doesn't handle kNoGC interrupts correctly.
DisableGCMole no_gc_mole;
uint32_t interrupt_budget = kGlobalInterruptBudget;
FastJsonStringifierResult result;
while (true) {
--interrupt_budget;
if (V8_UNLIKELY(interrupt_budget == 0)) {
result = HandleInterruptAndCheckCycle();
if (V8_UNLIKELY(result != SUCCESS)) return result;
interrupt_budget = kGlobalInterruptBudget;
}
switch (cont.type()) {
case ContinuationRecord::kObject: {
result = SerializeJSObject(cont.js_object(), no_gc);
break;
}
case ContinuationRecord::kObjectResume_Uninitialized: {
result = ResumeJSObject<FastIterableState::kUnknown>(
cont.js_object(), cont.object_descriptor_idx(),
cont.object_nof_descriptors(), cont.object_in_object_properties(),
cont.object_in_object_properties_start(), cont.object_descriptors(),
true, no_gc);
break;
}
case ContinuationRecord::kObjectResume_FastIterable: {
result = ResumeJSObject<FastIterableState::kJsonFast>(
cont.js_object(), cont.object_descriptor_idx(),
cont.object_nof_descriptors(), cont.object_in_object_properties(),
cont.object_in_object_properties_start(), cont.object_descriptors(),
true, no_gc);
break;
}
case ContinuationRecord::kObjectResume_SlowIterable: {
result = ResumeJSObject<FastIterableState::kJsonSlow>(
cont.js_object(), cont.object_descriptor_idx(),
cont.object_nof_descriptors(), cont.object_in_object_properties(),
cont.object_in_object_properties_start(), cont.object_descriptors(),
true, no_gc);
break;
}
case ContinuationRecord::kArray: {
result = SerializeJSArray(cont.js_array());
break;
}
case ContinuationRecord::kArrayResume: {
result = SerializeFixedArray<PACKED_ELEMENTS>(
cont.array_elements(), cont.array_index(), cont.array_length());
break;
}
case ContinuationRecord::kArrayResume_Holey: {
result = SerializeFixedArray<HOLEY_ELEMENTS>(
cont.array_elements(), cont.array_index(), cont.array_length());
break;
}
case ContinuationRecord::kArrayResume_WithInterrupts: {
result = SerializeFixedArrayWithInterruptCheck<PACKED_ELEMENTS>(
cont.array_elements(), cont.array_index(), cont.array_length());
break;
}
case ContinuationRecord::kArrayResume_Holey_WithInterrupts: {
result = SerializeFixedArrayWithInterruptCheck<HOLEY_ELEMENTS>(
cont.array_elements(), cont.array_index(), cont.array_length());
break;
}
default:
UNREACHABLE();
}
static_assert(SUCCESS == 0);
static_assert(JS_OBJECT == 1);
static_assert(JS_ARRAY == 2);
if (V8_UNLIKELY(result > JS_ARRAY)) return result;
if (stack_.empty()) return SUCCESS;
cont = stack_.back();
stack_.pop_back();
}
}
template <typename Char>
FastJsonStringifierResult
FastJsonStringifier<Char>::HandleInterruptAndCheckCycle() {
StackLimitCheck interrupt_check(isolate_);
{
// A TerminationException can actually trigger a GC when allocating the
// message object. We don't touch any of the heap objects after we
// encountered an exception, so this is fine.
AllowGarbageCollection allow_gc;
if (V8_UNLIKELY(interrupt_check.InterruptRequested() &&
IsException(isolate_->stack_guard()->HandleInterrupts(
StackGuard::InterruptLevel::kNoGC),
isolate_))) {
return EXCEPTION;
}
}
if (V8_UNLIKELY(CheckCycle())) {
// TODO(pthier): Construct exception message on fast-path and avoid falling
// back to slow path just to handle the exception.
return SLOW_PATH;
}
return SUCCESS;
}
template <typename Char>
bool FastJsonStringifier<Char>::CheckCycle() {
std::unordered_set<Address> set;
for (uint32_t i = 0; i < stack_.size(); i++) {
ContinuationRecord rec = stack_[i];
if (rec.type() == ContinuationRecord::kObjectKey ||
rec.type() == ContinuationRecord::kSimpleObject)
continue;
Tagged<Object> obj = rec.object();
if (V8_UNLIKELY(set.find(obj.ptr()) != set.end())) {
return true;
}
set.insert(obj.ptr());
}
return false;
}
template <typename Char>
template <typename SrcChar>
requires(sizeof(SrcChar) == sizeof(uint8_t))
bool FastJsonStringifier<Char>::AppendString(
const SrcChar* chars, size_t length,
const DisallowGarbageCollection& no_gc) {
constexpr int kUseSimdLengthThreshold = 32;
if (length >= kUseSimdLengthThreshold) {
return AppendStringSIMD(chars, length, no_gc);
}
return AppendStringSWAR(chars, length, 0, 0, no_gc);
}
template <typename Char>
template <typename SrcChar>
void FastJsonStringifier<Char>::AppendStringNoEscapes(
const SrcChar* chars, size_t length,
const DisallowGarbageCollection& no_gc) {
buffer_.Append(chars, length);
}
template <typename Char>
template <typename SrcChar>
requires(sizeof(SrcChar) == sizeof(uint8_t))
bool FastJsonStringifier<Char>::AppendStringScalar(
const SrcChar* chars, size_t length, size_t start,
size_t uncopied_src_index, const DisallowGarbageCollection& no_gc) {
bool needs_escaping = false;
for (size_t i = start; i < length; i++) {
SrcChar c = chars[i];
if (V8_LIKELY(DoNotEscape(c))) continue;
needs_escaping = true;
buffer_.Append(chars + uncopied_src_index, i - uncopied_src_index);
AppendCStringUnchecked(&JsonEscapeTable[c * kJsonEscapeTableEntrySize]);
uncopied_src_index = i + 1;
}
if (V8_LIKELY(uncopied_src_index < length)) {
buffer_.Append(chars + uncopied_src_index, length - uncopied_src_index);
}
return needs_escaping;
}
template <typename Char>
template <typename SrcChar>
requires(sizeof(SrcChar) == sizeof(uint8_t))
V8_CLANG_NO_SANITIZE("alignment")
bool FastJsonStringifier<Char>::AppendStringSWAR(
const SrcChar* chars, size_t length, size_t start,
size_t uncopied_src_index, const DisallowGarbageCollection& no_gc) {
using PackedT = uint32_t;
static constexpr size_t stride = sizeof(PackedT);
size_t i = start;
for (; i + (stride - 1) < length; i += stride) {
PackedT packed = *reinterpret_cast<const PackedT*>(chars + i);
if (V8_UNLIKELY(NeedsEscape(packed))) break;
}
return AppendStringScalar(chars, length, i, uncopied_src_index, no_gc);
}
template <typename Char>
template <typename SrcChar>
requires(sizeof(SrcChar) == sizeof(uint8_t))
bool FastJsonStringifier<Char>::AppendStringSIMD(
const SrcChar* chars, size_t length,
const DisallowGarbageCollection& no_gc) {
namespace hw = hwy::HWY_NAMESPACE;
bool needs_escaping = false;
size_t uncopied_src_index = 0; // Index of first char not copied yet.
const SrcChar* block = chars;
const SrcChar* end = chars + length;
hw::FixedTag<SrcChar, 16> tag;
static constexpr size_t stride = hw::Lanes(tag);
const auto mask_0x20 = hw::Set(tag, 0x20);
const auto mask_0x22 = hw::Set(tag, 0x22);
const auto mask_0x5c = hw::Set(tag, 0x5c);
for (; block + (stride - 1) < end; block += stride) {
const auto input = hw::LoadU(tag, block);
const auto has_lower_than_0x20 = input < mask_0x20;
const auto has_0x22 = input == mask_0x22;
const auto has_0x5c = input == mask_0x5c;
const auto result = hw::Or(hw::Or(has_lower_than_0x20, has_0x22), has_0x5c);
// No character that needs escaping found in block.
if (V8_LIKELY(hw::AllFalse(tag, result))) continue;
needs_escaping = true;
size_t index = hw::FindKnownFirstTrue(tag, result);
Char found_char = block[index];
const size_t char_index = block - chars + index;
const size_t copy_length = char_index - uncopied_src_index;
buffer_.Append(chars + uncopied_src_index, copy_length);
SBXCHECK_LT(found_char, 0x60);
AppendCStringUnchecked(
&JsonEscapeTable[found_char * kJsonEscapeTableEntrySize]);
uncopied_src_index = char_index + 1;
// Advance to character after the one that was found to need escaping.
block += index + 1;
// Subtract stride as it will be added again at the beginning of the loop.
block -= stride;
}
// Handle remaining characters.
const size_t start_index = block - chars;
return AppendStringSWAR(chars, length, start_index, uncopied_src_index,
no_gc) ||
needs_escaping;
}
template <typename Char>
template <typename SrcChar>
requires(sizeof(SrcChar) == sizeof(base::uc16))
bool FastJsonStringifier<Char>::AppendString(
const SrcChar* chars, size_t length,
const DisallowGarbageCollection& no_gc) {
bool needs_escaping = false;
uint32_t uncopied_src_index = 0; // Index of first char not copied yet.
// TODO(pthier): Add SIMD version.
for (uint32_t i = 0; i < length; i++) {
SrcChar c = chars[i];
if (V8_LIKELY(DoNotEscape(c))) continue;
needs_escaping = true;
if (sizeof(SrcChar) != 1 && base::IsInRange(c, static_cast<SrcChar>(0xD800),
static_cast<SrcChar>(0xDFFF))) {
// The current character is a surrogate.
buffer_.Append(chars + uncopied_src_index, i - uncopied_src_index);
char double_to_radix_chars[kDoubleToRadixMaxChars];
base::Vector<char> double_to_radix_buffer =
base::ArrayVector(double_to_radix_chars);
if (c <= 0xDBFF) {
// The current character is a leading surrogate.
if (i + 1 < length) {
// There is a next character.
SrcChar next = chars[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.
AppendCharacterUnchecked(c);
AppendCharacterUnchecked(next);
i++;
} else {
// The next character is not a trailing surrogate. Thus, the
// current character is a lone leading surrogate.
AppendCStringLiteralUnchecked("\\u");
std::string_view hex =
DoubleToRadixStringView(c, 16, double_to_radix_buffer);
AppendStringUnchecked(hex);
}
} else {
// There is no next character. Thus, the current character is a lone
// leading surrogate.
AppendCStringLiteralUnchecked("\\u");
std::string_view hex =
DoubleToRadixStringView(c, 16, double_to_radix_buffer);
AppendStringUnchecked(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.)
AppendCStringLiteralUnchecked("\\u");
std::string_view hex =
DoubleToRadixStringView(c, 16, double_to_radix_buffer);
AppendStringUnchecked(hex);
}
uncopied_src_index = i + 1;
} else {
buffer_.Append(chars + uncopied_src_index, i - uncopied_src_index);
DCHECK_LT(c, 0x60);
AppendCStringUnchecked(&JsonEscapeTable[c * kJsonEscapeTableEntrySize]);
uncopied_src_index = i + 1;
}
}
if (uncopied_src_index < length) {
buffer_.Append(chars + uncopied_src_index, length - uncopied_src_index);
}
return needs_escaping;
}
namespace {
bool CanUseFastStringifier(DirectHandle<JSAny> replacer,
DirectHandle<Object> gap) {
// TODO(pthier): Support gap on fast-path.
return v8_flags.json_stringify_fast_path && IsUndefined(*replacer) &&
IsUndefined(*gap);
}
MaybeDirectHandle<Object> FastJsonStringify(Isolate* isolate,
Handle<JSAny> object) {
DisallowGarbageCollection no_gc;
FastJsonStringifier<uint8_t> one_byte_stringifier(isolate);
std::optional<FastJsonStringifier<base::uc16>> two_byte_stringifier;
FastJsonStringifierResult result =
one_byte_stringifier.SerializeObject(*object, no_gc);
bool result_is_one_byte = true;
if (result == CHANGE_ENCODING) {
two_byte_stringifier.emplace(isolate);
result = two_byte_stringifier->ResumeFrom(one_byte_stringifier, no_gc);
DCHECK_NE(result, CHANGE_ENCODING);
result_is_one_byte = false;
}
if (V8_LIKELY(result == SUCCESS)) {
if (result_is_one_byte) {
const size_t length = one_byte_stringifier.ResultLength();
DirectHandle<SeqOneByteString> ret;
{
AllowGarbageCollection allow_gc;
if (length > String::kMaxLength) {
THROW_NEW_ERROR(isolate, NewInvalidStringLengthError());
}
ASSIGN_RETURN_ON_EXCEPTION(
isolate, ret,
isolate->factory()->NewRawOneByteString(static_cast<int>(length)));
}
one_byte_stringifier.CopyResultTo(ret->GetChars(no_gc));
return ret;
} else {
DCHECK(two_byte_stringifier.has_value());
const size_t one_byte_length = one_byte_stringifier.ResultLength();
const size_t two_byte_length = two_byte_stringifier->ResultLength();
const size_t total_length = one_byte_length + two_byte_length;
DirectHandle<SeqTwoByteString> ret;
{
AllowGarbageCollection allow_gc;
if (total_length > String::kMaxLength) {
THROW_NEW_ERROR(isolate, NewInvalidStringLengthError());
}
ASSIGN_RETURN_ON_EXCEPTION(isolate, ret,
isolate->factory()->NewRawTwoByteString(
static_cast<int>(total_length)));
}
base::uc16* chars = ret->GetChars(no_gc);
if (one_byte_length > 0) {
one_byte_stringifier.CopyResultTo(chars);
}
DCHECK_GT(two_byte_length, 0);
two_byte_stringifier->CopyResultTo(chars + one_byte_length);
return ret;
}
} else if (result == UNDEFINED) {
return isolate->factory()->undefined_value();
} else if (result == SLOW_PATH) {
// TODO(pthier): Resume instead of restarting.
AllowGarbageCollection allow_gc;
JsonStringifier stringifier(isolate);
Handle<JSAny> undefined = isolate->factory()->undefined_value();
return stringifier.Stringify(object, undefined, undefined);
}
DCHECK(result == EXCEPTION);
CHECK(isolate->has_exception());
return MaybeDirectHandle<Object>();
}
} // namespace
MaybeDirectHandle<Object> JsonStringify(Isolate* isolate, Handle<JSAny> object,
Handle<JSAny> replacer,
Handle<Object> gap) {
if (CanUseFastStringifier(replacer, gap)) {
return FastJsonStringify(isolate, object);
} else {
JsonStringifier stringifier(isolate);
return stringifier.Stringify(object, replacer, gap);
}
}
} // namespace internal
} // namespace v8