blob: 9be4924268cd1bccfa26027a4c400cf4613ae847 [file] [log] [blame]
// 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-parser.h"
#include "src/base/strings.h"
#include "src/builtins/builtins.h"
#include "src/common/assert-scope.h"
#include "src/common/globals.h"
#include "src/common/message-template.h"
#include "src/debug/debug.h"
#include "src/execution/frames-inl.h"
#include "src/heap/factory.h"
#include "src/numbers/conversions.h"
#include "src/numbers/hash-seed-inl.h"
#include "src/objects/elements-kind.h"
#include "src/objects/field-type.h"
#include "src/objects/hash-table-inl.h"
#include "src/objects/map-updater.h"
#include "src/objects/objects-inl.h"
#include "src/objects/property-descriptor.h"
#include "src/objects/property-details.h"
#include "src/roots/roots.h"
#include "src/strings/char-predicates-inl.h"
#include "src/strings/string-hasher.h"
#include "src/utils/boxed-float.h"
namespace v8 {
namespace internal {
namespace {
constexpr JsonToken GetOneCharJsonToken(uint8_t c) {
// clang-format off
return
c == '"' ? JsonToken::STRING :
IsDecimalDigit(c) ? JsonToken::NUMBER :
c == '-' ? JsonToken::NUMBER :
c == '[' ? JsonToken::LBRACK :
c == '{' ? JsonToken::LBRACE :
c == ']' ? JsonToken::RBRACK :
c == '}' ? JsonToken::RBRACE :
c == 't' ? JsonToken::TRUE_LITERAL :
c == 'f' ? JsonToken::FALSE_LITERAL :
c == 'n' ? JsonToken::NULL_LITERAL :
c == ' ' ? JsonToken::WHITESPACE :
c == '\t' ? JsonToken::WHITESPACE :
c == '\r' ? JsonToken::WHITESPACE :
c == '\n' ? JsonToken::WHITESPACE :
c == ':' ? JsonToken::COLON :
c == ',' ? JsonToken::COMMA :
JsonToken::ILLEGAL;
// clang-format on
}
// Table of one-character tokens, by character (0x00..0xFF only).
static const constexpr JsonToken one_char_json_tokens[256] = {
#define CALL_GET_SCAN_FLAGS(N) GetOneCharJsonToken(N),
INT_0_TO_127_LIST(CALL_GET_SCAN_FLAGS)
#undef CALL_GET_SCAN_FLAGS
#define CALL_GET_SCAN_FLAGS(N) GetOneCharJsonToken(128 + N),
INT_0_TO_127_LIST(CALL_GET_SCAN_FLAGS)
#undef CALL_GET_SCAN_FLAGS
};
enum class EscapeKind : uint8_t {
kIllegal,
kSelf,
kBackspace,
kTab,
kNewLine,
kFormFeed,
kCarriageReturn,
kUnicode
};
using EscapeKindField = base::BitField8<EscapeKind, 0, 3>;
using MayTerminateStringField = EscapeKindField::Next<bool, 1>;
using NumberPartField = MayTerminateStringField::Next<bool, 1>;
constexpr bool MayTerminateJsonString(uint8_t flags) {
return MayTerminateStringField::decode(flags);
}
constexpr EscapeKind GetEscapeKind(uint8_t flags) {
return EscapeKindField::decode(flags);
}
constexpr bool IsNumberPart(uint8_t flags) {
return NumberPartField::decode(flags);
}
constexpr uint8_t GetJsonScanFlags(uint8_t c) {
// clang-format off
return (c == 'b' ? EscapeKindField::encode(EscapeKind::kBackspace)
: c == 't' ? EscapeKindField::encode(EscapeKind::kTab)
: c == 'n' ? EscapeKindField::encode(EscapeKind::kNewLine)
: c == 'f' ? EscapeKindField::encode(EscapeKind::kFormFeed)
: c == 'r' ? EscapeKindField::encode(EscapeKind::kCarriageReturn)
: c == 'u' ? EscapeKindField::encode(EscapeKind::kUnicode)
: c == '"' ? EscapeKindField::encode(EscapeKind::kSelf)
: c == '\\' ? EscapeKindField::encode(EscapeKind::kSelf)
: c == '/' ? EscapeKindField::encode(EscapeKind::kSelf)
: EscapeKindField::encode(EscapeKind::kIllegal)) |
(c < 0x20 ? MayTerminateStringField::encode(true)
: c == '"' ? MayTerminateStringField::encode(true)
: c == '\\' ? MayTerminateStringField::encode(true)
: MayTerminateStringField::encode(false)) |
NumberPartField::encode(c == '.' ||
c == 'e' ||
c == 'E' ||
IsDecimalDigit(c) ||
c == '-' ||
c == '+');
// clang-format on
}
// Table of one-character scan flags, by character (0x00..0xFF only).
static const constexpr uint8_t character_json_scan_flags[256] = {
#define CALL_GET_SCAN_FLAGS(N) GetJsonScanFlags(N),
INT_0_TO_127_LIST(CALL_GET_SCAN_FLAGS)
#undef CALL_GET_SCAN_FLAGS
#define CALL_GET_SCAN_FLAGS(N) GetJsonScanFlags(128 + N),
INT_0_TO_127_LIST(CALL_GET_SCAN_FLAGS)
#undef CALL_GET_SCAN_FLAGS
};
} // namespace
MaybeHandle<Object> JsonParseInternalizer::Internalize(
Isolate* isolate, Handle<Object> result, Handle<Object> reviver,
Handle<String> source, MaybeHandle<Object> val_node) {
DCHECK(IsCallable(*reviver));
JsonParseInternalizer internalizer(isolate, Cast<JSReceiver>(reviver),
source);
Handle<JSObject> holder =
isolate->factory()->NewJSObject(isolate->object_function());
Handle<String> name = isolate->factory()->empty_string();
JSObject::AddProperty(isolate, holder, name, result, NONE);
return internalizer.InternalizeJsonProperty<kWithSource>(
holder, name, val_node.ToHandleChecked(), result);
}
template <JsonParseInternalizer::WithOrWithoutSource with_source>
MaybeHandle<Object> JsonParseInternalizer::InternalizeJsonProperty(
Handle<JSReceiver> holder, Handle<String> name, Handle<Object> val_node,
Handle<Object> snapshot) {
DCHECK_EQ(with_source == kWithSource,
!val_node.is_null() && !snapshot.is_null());
DCHECK(IsCallable(*reviver_));
HandleScope outer_scope(isolate_);
Handle<Object> value;
ASSIGN_RETURN_ON_EXCEPTION(
isolate_, value, Object::GetPropertyOrElement(isolate_, holder, name));
// When with_source == kWithSource, the source text is passed to the reviver
// if the reviver has not mucked with the originally parsed value.
//
// When with_source == kWithoutSource, this is unused.
bool pass_source_to_reviver =
with_source == kWithSource && Object::SameValue(*value, *snapshot);
if (IsJSReceiver(*value)) {
Handle<JSReceiver> object = Cast<JSReceiver>(value);
Maybe<bool> is_array = Object::IsArray(object);
if (is_array.IsNothing()) return MaybeHandle<Object>();
if (is_array.FromJust()) {
Handle<Object> length_object;
ASSIGN_RETURN_ON_EXCEPTION(
isolate_, length_object,
Object::GetLengthFromArrayLike(isolate_, object));
double length = Object::NumberValue(*length_object);
if (pass_source_to_reviver) {
auto val_nodes_and_snapshots = Cast<FixedArray>(val_node);
int snapshot_length = val_nodes_and_snapshots->length() / 2;
for (int i = 0; i < length; i++) {
HandleScope inner_scope(isolate_);
DirectHandle<Object> index = isolate_->factory()->NewNumber(i);
Handle<String> index_name =
isolate_->factory()->NumberToString(index);
// Even if the array pointer snapshot matched, it's possible the
// array had new elements added that are not in the snapshotted
// elements.
const bool rv =
i < snapshot_length
? RecurseAndApply<kWithSource>(
object, index_name,
handle(val_nodes_and_snapshots->get(i * 2), isolate_),
handle(val_nodes_and_snapshots->get(i * 2 + 1),
isolate_))
: RecurseAndApply<kWithoutSource>(
object, index_name, Handle<Object>(), Handle<Object>());
if (!rv) {
return MaybeHandle<Object>();
}
}
} else {
for (int i = 0; i < length; i++) {
HandleScope inner_scope(isolate_);
DirectHandle<Object> index = isolate_->factory()->NewNumber(i);
Handle<String> index_name =
isolate_->factory()->NumberToString(index);
if (!RecurseAndApply<kWithoutSource>(
object, index_name, Handle<Object>(), Handle<Object>())) {
return MaybeHandle<Object>();
}
}
}
} else {
Handle<FixedArray> contents;
ASSIGN_RETURN_ON_EXCEPTION(
isolate_, contents,
KeyAccumulator::GetKeys(isolate_, object, KeyCollectionMode::kOwnOnly,
ENUMERABLE_STRINGS,
GetKeysConversion::kConvertToString));
if (pass_source_to_reviver) {
auto val_nodes_and_snapshots = Cast<ObjectTwoHashTable>(val_node);
for (int i = 0; i < contents->length(); i++) {
HandleScope inner_scope(isolate_);
Handle<String> key_name(Cast<String>(contents->get(i)), isolate_);
auto property_val_node_and_snapshot =
val_nodes_and_snapshots->Lookup(isolate_, key_name);
Handle<Object> property_val_node(property_val_node_and_snapshot[0],
isolate_);
Handle<Object> property_snapshot(property_val_node_and_snapshot[1],
isolate_);
// Even if the object pointer snapshot matched, it's possible the
// object had new properties added that are not in the snapshotted
// contents.
const bool rv =
!IsTheHole(*property_snapshot)
? RecurseAndApply<kWithSource>(
object, key_name, property_val_node, property_snapshot)
: RecurseAndApply<kWithoutSource>(
object, key_name, Handle<Object>(), Handle<Object>());
if (!rv) {
return MaybeHandle<Object>();
}
}
} else {
for (int i = 0; i < contents->length(); i++) {
HandleScope inner_scope(isolate_);
Handle<String> key_name(Cast<String>(contents->get(i)), isolate_);
if (!RecurseAndApply<kWithoutSource>(
object, key_name, Handle<Object>(), Handle<Object>())) {
return MaybeHandle<Object>();
}
}
}
}
}
Handle<JSObject> context =
isolate_->factory()->NewJSObject(isolate_->object_function());
if (pass_source_to_reviver && IsString(*val_node)) {
JSReceiver::CreateDataProperty(isolate_, context,
isolate_->factory()->source_string(),
val_node, Just(kThrowOnError))
.Check();
}
Handle<Object> argv[] = {name, value, context};
Handle<Object> result;
ASSIGN_RETURN_ON_EXCEPTION(
isolate_, result, Execution::Call(isolate_, reviver_, holder, 3, argv));
return outer_scope.CloseAndEscape(result);
}
template <JsonParseInternalizer::WithOrWithoutSource with_source>
bool JsonParseInternalizer::RecurseAndApply(Handle<JSReceiver> holder,
Handle<String> name,
Handle<Object> val_node,
Handle<Object> snapshot) {
STACK_CHECK(isolate_, false);
DCHECK(IsCallable(*reviver_));
Handle<Object> result;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate_, result,
InternalizeJsonProperty<with_source>(holder, name, val_node, snapshot),
false);
Maybe<bool> change_result = Nothing<bool>();
if (IsUndefined(*result, isolate_)) {
change_result = JSReceiver::DeletePropertyOrElement(holder, name,
LanguageMode::kSloppy);
} else {
PropertyDescriptor desc;
desc.set_value(Cast<JSAny>(result));
desc.set_configurable(true);
desc.set_enumerable(true);
desc.set_writable(true);
change_result = JSReceiver::DefineOwnProperty(isolate_, holder, name, &desc,
Just(kDontThrow));
}
MAYBE_RETURN(change_result, false);
return true;
}
template <typename Char>
JsonParser<Char>::JsonParser(Isolate* isolate, Handle<String> source)
: isolate_(isolate),
hash_seed_(HashSeed(isolate)),
object_constructor_(isolate_->object_function()),
original_source_(source) {
size_t start = 0;
size_t length = source->length();
PtrComprCageBase cage_base(isolate);
if (IsSlicedString(*source, cage_base)) {
Tagged<SlicedString> string = Cast<SlicedString>(*source);
start = string->offset();
Tagged<String> parent = string->parent();
if (IsThinString(parent, cage_base))
parent = Cast<ThinString>(parent)->actual();
source_ = handle(parent, isolate);
} else {
source_ = String::Flatten(isolate, source);
}
if (StringShape(*source_, cage_base).IsExternal()) {
chars_ =
static_cast<const Char*>(Cast<SeqExternalString>(*source_)->GetChars());
chars_may_relocate_ = false;
} else {
DisallowGarbageCollection no_gc;
isolate->main_thread_local_heap()->AddGCEpilogueCallback(
UpdatePointersCallback, this);
chars_ = Cast<SeqString>(*source_)->GetChars(no_gc);
chars_may_relocate_ = true;
}
cursor_ = chars_ + start;
end_ = cursor_ + length;
}
template <typename Char>
bool JsonParser<Char>::IsSpecialString() {
// The special cases are undefined, NaN, Infinity, and {} being passed to the
// parse method
int offset = IsSlicedString(*original_source_)
? Cast<SlicedString>(*original_source_)->offset()
: 0;
size_t length = original_source_->length();
#define CASES(V) \
V("[object Object]") \
V("undefined") \
V("Infinity") \
V("NaN")
switch (length) {
#define CASE(n) \
case arraysize(n) - 1: \
return CompareCharsEqual(chars_ + offset, n, arraysize(n) - 1);
CASES(CASE)
default:
return false;
}
#undef CASE
#undef CASES
}
template <typename Char>
MessageTemplate JsonParser<Char>::GetErrorMessageWithEllipses(
Handle<Object>& arg, Handle<Object>& arg2, int pos) {
MessageTemplate message;
Factory* factory = this->factory();
arg = factory->LookupSingleCharacterStringFromCode(*cursor_);
int origin_source_length = original_source_->length();
// only provide context for strings with at least
// kMinOriginalSourceLengthForContext charcacters in length
if (origin_source_length >= kMinOriginalSourceLengthForContext) {
int substring_start = 0;
int substring_end = origin_source_length;
if (pos < kMaxContextCharacters) {
message =
MessageTemplate::kJsonParseUnexpectedTokenStartStringWithContext;
// Output the string followed by elipses
substring_end = pos + kMaxContextCharacters;
} else if (pos >= kMaxContextCharacters &&
pos < origin_source_length - kMaxContextCharacters) {
message =
MessageTemplate::kJsonParseUnexpectedTokenSurroundStringWithContext;
// Add context before and after position of bad token surrounded by
// elipses
substring_start = pos - kMaxContextCharacters;
substring_end = pos + kMaxContextCharacters;
} else {
message = MessageTemplate::kJsonParseUnexpectedTokenEndStringWithContext;
// Add ellipses followed by some context before bad token
substring_start = pos - kMaxContextCharacters;
}
arg2 =
factory->NewSubString(original_source_, substring_start, substring_end);
} else {
arg2 = original_source_;
// Output the entire string without ellipses but provide the token which
// was unexpected
message = MessageTemplate::kJsonParseUnexpectedTokenShortString;
}
return message;
}
template <typename Char>
MessageTemplate JsonParser<Char>::LookUpErrorMessageForJsonToken(
JsonToken token, Handle<Object>& arg, Handle<Object>& arg2, int pos) {
MessageTemplate message;
switch (token) {
case JsonToken::EOS:
message = MessageTemplate::kJsonParseUnexpectedEOS;
break;
case JsonToken::NUMBER:
message = MessageTemplate::kJsonParseUnexpectedTokenNumber;
break;
case JsonToken::STRING:
message = MessageTemplate::kJsonParseUnexpectedTokenString;
break;
default:
// Output entire string without ellipses and don't provide the token
// that was unexpected because it makes the error messages more confusing
if (IsSpecialString()) {
arg = original_source_;
message = MessageTemplate::kJsonParseShortString;
} else {
message = GetErrorMessageWithEllipses(arg, arg2, pos);
}
}
return message;
}
template <typename Char>
void JsonParser<Char>::CalculateFileLocation(Handle<Object>& line,
Handle<Object>& column) {
// JSON allows only \r and \n as line terminators.
// (See https://www.json.org/json-en.html - "whitespace")
int line_number = 1;
const Char* start =
chars_ + (IsSlicedString(*original_source_)
? Cast<SlicedString>(*original_source_)->offset()
: 0);
const Char* last_line_break = start;
const Char* cursor = start;
const Char* end = cursor_; // cursor_ points to the position of the error.
for (; cursor < end; ++cursor) {
if (*cursor == '\r' && cursor < end - 1 && cursor[1] == '\n') {
// \r\n counts as a single line terminator, as of
// https://tc39.es/ecma262/#sec-line-terminators. JSON itself does not
// have a notion of lines or line terminators.
++cursor;
}
if (*cursor == '\r' || *cursor == '\n') {
++line_number;
last_line_break = cursor + 1;
}
}
int column_number = 1 + static_cast<int>(cursor - last_line_break);
line = handle(Smi::FromInt(line_number), isolate());
column = handle(Smi::FromInt(column_number), isolate());
}
template <typename Char>
void JsonParser<Char>::ReportUnexpectedToken(
JsonToken token, base::Optional<MessageTemplate> errorMessage) {
// Some exception (for example stack overflow) was already thrown.
if (isolate_->has_exception()) return;
// Parse failed. Current character is the unexpected token.
Factory* factory = this->factory();
int offset = IsSlicedString(*original_source_)
? Cast<SlicedString>(*original_source_)->offset()
: 0;
int pos = position() - offset;
Handle<Object> arg(Smi::FromInt(pos), isolate());
Handle<Object> arg2;
Handle<Object> arg3;
CalculateFileLocation(arg2, arg3);
MessageTemplate message =
errorMessage ? errorMessage.value()
: LookUpErrorMessageForJsonToken(token, arg, arg2, pos);
Handle<Script> script(factory->NewScript(original_source_));
DCHECK_IMPLIES(isolate_->NeedsSourcePositions(), script->has_line_ends());
DebuggableStackFrameIterator it(isolate_);
if (!it.done() && it.is_javascript()) {
FrameSummary summary = it.GetTopValidFrame();
script->set_eval_from_shared(summary.AsJavaScript().function()->shared());
if (IsScript(*summary.script())) {
script->set_origin_options(
Cast<Script>(*summary.script())->origin_options());
}
}
// We should sent compile error event because we compile JSON object in
// separated source file.
isolate()->debug()->OnCompileError(script);
MessageLocation location(script, pos, pos + 1);
isolate()->ThrowAt(factory->NewSyntaxError(message, arg, arg2, arg3),
&location);
// Move the cursor to the end so we won't be able to proceed parsing.
cursor_ = end_;
}
template <typename Char>
void JsonParser<Char>::ReportUnexpectedCharacter(base::uc32 c) {
JsonToken token = JsonToken::ILLEGAL;
if (c == kEndOfString) {
token = JsonToken::EOS;
} else if (c <= unibrow::Latin1::kMaxChar) {
token = one_char_json_tokens[c];
}
return ReportUnexpectedToken(token);
}
template <typename Char>
JsonParser<Char>::~JsonParser() {
if (StringShape(*source_).IsExternal()) {
// Check that the string shape hasn't changed. Otherwise our GC hooks are
// broken.
Cast<SeqExternalString>(*source_);
} else {
// Check that the string shape hasn't changed. Otherwise our GC hooks are
// broken.
Cast<SeqString>(*source_);
isolate()->main_thread_local_heap()->RemoveGCEpilogueCallback(
UpdatePointersCallback, this);
}
}
template <typename Char>
MaybeHandle<Object> JsonParser<Char>::ParseJson(DirectHandle<Object> reviver) {
Handle<Object> result;
// Only record the val node when reviver is callable.
bool reviver_is_callable = IsCallable(*reviver);
bool should_track_json_source = reviver_is_callable;
if (V8_UNLIKELY(should_track_json_source)) {
ASSIGN_RETURN_ON_EXCEPTION(isolate(), result, ParseJsonValue<true>());
} else {
ASSIGN_RETURN_ON_EXCEPTION(isolate(), result, ParseJsonValueRecursive());
}
if (!Check(JsonToken::EOS)) {
ReportUnexpectedToken(
peek(), MessageTemplate::kJsonParseUnexpectedNonWhiteSpaceCharacter);
return MaybeHandle<Object>();
}
if (isolate_->has_exception()) {
return MaybeHandle<Object>();
}
return result;
}
MaybeHandle<Object> InternalizeJsonProperty(Handle<JSObject> holder,
Handle<String> key);
namespace {
template <typename Char>
JsonToken GetTokenForCharacter(Char c) {
return V8_LIKELY(c <= unibrow::Latin1::kMaxChar) ? one_char_json_tokens[c]
: JsonToken::ILLEGAL;
}
} // namespace
template <typename Char>
void JsonParser<Char>::SkipWhitespace() {
JsonToken local_next = JsonToken::EOS;
cursor_ = std::find_if(cursor_, end_, [&](Char c) {
JsonToken current = GetTokenForCharacter(c);
bool result = current != JsonToken::WHITESPACE;
if (V8_LIKELY(result)) local_next = current;
return result;
});
next_ = local_next;
}
template <typename Char>
base::uc32 JsonParser<Char>::ScanUnicodeCharacter() {
base::uc32 value = 0;
for (int i = 0; i < 4; i++) {
int digit = base::HexValue(NextCharacter());
if (V8_UNLIKELY(digit < 0)) return kInvalidUnicodeCharacter;
value = value * 16 + digit;
}
return value;
}
// Parse any JSON value.
template <typename Char>
JsonString JsonParser<Char>::ScanJsonPropertyKey(JsonContinuation* cont) {
{
DisallowGarbageCollection no_gc;
const Char* start = cursor_;
base::uc32 first = CurrentCharacter();
if (first == '\\' && NextCharacter() == 'u') first = ScanUnicodeCharacter();
if (IsDecimalDigit(first)) {
if (first == '0') {
if (NextCharacter() == '"') {
advance();
// Record element information.
cont->elements++;
DCHECK_LE(0, cont->max_index);
return JsonString(0);
}
} else {
uint32_t index = first - '0';
while (true) {
cursor_ = std::find_if(cursor_ + 1, end_, [&index](Char c) {
return !TryAddArrayIndexChar(&index, c);
});
if (CurrentCharacter() == '"') {
advance();
// Record element information.
cont->elements++;
cont->max_index = std::max(cont->max_index, index);
return JsonString(index);
}
if (CurrentCharacter() == '\\' && NextCharacter() == 'u') {
if (TryAddArrayIndexChar(&index, ScanUnicodeCharacter())) continue;
}
break;
}
}
}
// Reset cursor_ to start if the key is not an index.
cursor_ = start;
}
return ScanJsonString(true);
}
class FoldedMutableHeapNumberAllocation {
public:
// TODO(leszeks): If allocation alignment is ever enabled, we'll need to add
// padding fillers between heap numbers.
static_assert(!USE_ALLOCATION_ALIGNMENT_BOOL);
FoldedMutableHeapNumberAllocation(Isolate* isolate, int count) {
if (count == 0) return;
int size = count * sizeof(HeapNumber);
raw_bytes_ = isolate->factory()->NewByteArray(size);
}
Handle<ByteArray> raw_bytes() const { return raw_bytes_; }
private:
Handle<ByteArray> raw_bytes_ = {};
};
class FoldedMutableHeapNumberAllocator {
public:
FoldedMutableHeapNumberAllocator(
Isolate* isolate, FoldedMutableHeapNumberAllocation* allocation,
DisallowGarbageCollection& no_gc)
: isolate_(isolate), roots_(isolate) {
if (allocation->raw_bytes().is_null()) return;
raw_bytes_ = allocation->raw_bytes();
mutable_double_address_ =
reinterpret_cast<Address>(allocation->raw_bytes()->begin());
}
~FoldedMutableHeapNumberAllocator() {
// Make all mutable HeapNumbers alive.
if (mutable_double_address_ == 0) {
DCHECK(raw_bytes_.is_null());
return;
}
DCHECK_EQ(mutable_double_address_,
reinterpret_cast<Address>(raw_bytes_->end()));
// Before setting the length of mutable_double_buffer back to zero, we
// must ensure that the sweeper is not running or has already swept the
// object's page. Otherwise the GC can add the contents of
// mutable_double_buffer to the free list.
isolate_->heap()->EnsureSweepingCompletedForObject(*raw_bytes_);
raw_bytes_->set_length(0);
}
Tagged<HeapNumber> AllocateNext(ReadOnlyRoots roots, Float64 value) {
DCHECK_GE(mutable_double_address_,
reinterpret_cast<Address>(raw_bytes_->begin()));
Tagged<HeapObject> hn = HeapObject::FromAddress(mutable_double_address_);
hn->set_map_after_allocation(roots.heap_number_map());
Cast<HeapNumber>(hn)->set_value_as_bits(value.get_bits());
mutable_double_address_ +=
ALIGN_TO_ALLOCATION_ALIGNMENT(sizeof(HeapNumber));
DCHECK_LE(mutable_double_address_,
reinterpret_cast<Address>(raw_bytes_->end()));
return Cast<HeapNumber>(hn);
}
private:
Isolate* isolate_;
ReadOnlyRoots roots_;
Handle<ByteArray> raw_bytes_ = {};
Address mutable_double_address_ = 0;
};
// JSDataObjectBuilder is a helper for efficiently building a data object,
// similar (in semantics and efficiency) to a JS object literal, based on
// key/value pairs.
//
// The JSDataObjectBuilder works by first trying to find the right map for the
// object, and then letting the caller stamp out the object fields linearly.
// There are several fast paths that can be fallen out of; if the builder bails
// out, then it's still possible to stamp out the object partially based on the
// last map found, and then continue with slow object setup afterward.
//
// The maps start from the object literal cache (to try to share maps with
// equivalent object literals in JS code). From there, when adding properties,
// there are several fast paths that the builder follows:
//
// 1. At construction, it can be passed an expected final map for the object
// (e.g. cached from previous runs, or assumed from surrounding objects).
// If given, then we first check whether the property matches the
// entry in the DescriptorArray of the final map; if yes, then we don't
// need to do any map transitions.
// 2. When given a property key, it looks for whether there is exactly one
// transition away from the current map ("ExpectedTransitionTarget").
// If yes, it tries to match against the key for this transition. The
// expected key is passed as a hint to the current property key getter,
// for e.g. faster internalised string materialisation.
// 3. Otherwise, it searches for whether there is any transition in the
// current map that matches the key.
// 4. For all of the above, it checks whether the field represntation of the
// found map matches the representation of the value. If it doesn't, it
// migrates the map, potentially deprecating it too.
// 5. If there is no transition, it tries to allocate a new map transition,
// bailing out if this fails.
class JSDataObjectBuilder {
public:
// HeapNumberMode determines whether incoming HeapNumber values will be
// guaranteed to be uniquely owned by this object, and therefore can be used
// directly as mutable HeapNumbers for double representation fields.
enum HeapNumberMode {
kNormalHeapNumbers,
kHeapNumbersGuaranteedUniquelyOwned
};
JSDataObjectBuilder(Isolate* isolate, ElementsKind elements_kind,
int expected_named_properties,
Handle<Map> expected_final_map,
HeapNumberMode heap_number_mode)
: isolate_(isolate),
elements_kind_(elements_kind),
expected_property_count_(expected_named_properties),
heap_number_mode_(heap_number_mode),
expected_final_map_(expected_final_map) {
if (!TryInitializeMapFromExpectedFinalMap()) {
InitializeMapFromZero();
}
}
// Builds and returns an object whose properties are based on a property
// iterator.
//
// Expects an iterator of the form:
//
// struct Iterator {
// void Advance();
// bool Done();
//
// // Get the key of the current property, optionally returning the hinted
// // expected key if applicable.
// Handle<String> GetKey(Handle<String> expected_key_hint);
//
// // Get the value of the current property. `will_revisit_value` is true
// // if this value will need to be revisited later via RevisitValues().
// Handle<Object> GetValue(bool will_revisit_value);
//
// // Return an iterator over the values that were already visited by
// // GetValue. Might require caching those values if necessary.
// ValueIterator RevisitValues();
// }
template <typename PropertyIterator>
Handle<JSObject> BuildFromIterator(
PropertyIterator&& it, MaybeHandle<FixedArrayBase> maybe_elements = {}) {
Handle<String> failed_property_add_key;
for (; !it.Done(); it.Advance()) {
Handle<String> property_key;
if (!TryAddFastPropertyForValue(
[&](Handle<String> expected_key) {
return property_key = it.GetKey(expected_key);
},
[&]() { return it.GetValue(true); })) {
failed_property_add_key = property_key;
break;
}
}
Handle<FixedArrayBase> elements;
if (!maybe_elements.ToHandle(&elements)) {
elements = isolate_->factory()->empty_fixed_array();
}
CreateAndInitialiseObject(it.RevisitValues(), elements);
// Slow path: define remaining named properties.
for (; !it.Done(); it.Advance()) {
Handle<String> key;
if (!failed_property_add_key.is_null()) {
key = std::exchange(failed_property_add_key, {});
} else {
key = it.GetKey({});
}
#ifdef DEBUG
uint32_t index;
DCHECK(!key->AsArrayIndex(&index));
#endif
Handle<Object> value = it.GetValue(false);
AddSlowProperty(key, value);
}
return object();
}
template <typename GetKeyFunction, typename GetValueFunction>
V8_INLINE bool TryAddFastPropertyForValue(GetKeyFunction&& get_key,
GetValueFunction&& get_value) {
// The fast path is only valid as long as we haven't allocated an object
// yet.
DCHECK(object_.is_null());
Handle<String> key;
bool existing_map_found = TryFastTransitionToPropertyKey(get_key, &key);
// Unconditionally get the value after getting the transition result.
DirectHandle<Object> value = get_value();
if (existing_map_found) {
// We found a map with a field for our value -- now make sure that field
// is compatible with our value.
if (!TryGeneralizeFieldToValue(value)) {
// TODO(leszeks): Try to stay on the fast path if we just deprecate
// here.
return false;
}
AdvanceToNextProperty();
return true;
}
// Try to stay on a semi-fast path (being able to stamp out the object
// fields after creating the correct map) by manually creating the next
// map here.
Tagged<DescriptorArray> descriptors = map_->instance_descriptors(isolate_);
InternalIndex descriptor_number =
descriptors->SearchWithCache(isolate_, *key, *map_);
if (descriptor_number.is_found()) {
// Duplicate property, we need to bail out of even the semi-fast path
// because we can no longer stamp out values linearly.
return false;
}
if (!TransitionsAccessor::CanHaveMoreTransitions(isolate_, map_)) {
return false;
}
Representation representation =
Object::OptimalRepresentation(*value, isolate_);
Handle<FieldType> type =
Object::OptimalType(*value, isolate_, representation);
MaybeHandle<Map> maybe_map = Map::CopyWithField(
isolate_, map_, key, type, NONE, PropertyConstness::kConst,
representation, INSERT_TRANSITION);
Handle<Map> next_map;
if (!maybe_map.ToHandle(&next_map)) return false;
if (next_map->is_dictionary_map()) return false;
map_ = next_map;
if (representation.IsDouble()) {
RegisterFieldNeedsFreshHeapNumber(value);
}
AdvanceToNextProperty();
return true;
}
template <typename ValueIterator>
V8_INLINE void CreateAndInitialiseObject(
ValueIterator value_it, DirectHandle<FixedArrayBase> elements) {
// We've created a map for the first `i` property stack values (which might
// be all of them). We need to write these properties to a newly allocated
// object.
DCHECK(object_.is_null());
if (current_property_index_ < property_count_in_expected_final_map_) {
// If we were on the expected map fast path all the way, but never reached
// the expected final map itself, then finalize the map by rewinding to
// the one whose property is the actual current property index.
//
// TODO(leszeks): Do we actually want to use the final map fast path when
// we know that the current map _can't_ reach the final map? Will we even
// hit this case given that we check for matching instance size?
RewindExpectedFinalMapFastPathToBeforeCurrent();
}
if (map_->is_dictionary_map()) {
// It's only safe to emit a dictionary map when we've not set up any
// properties, as the caller assumes it can set up the first N properties
// as fast data properties.
DCHECK_EQ(current_property_index_, 0);
Handle<JSObject> object = isolate_->factory()->NewSlowJSObjectFromMap(
map_, expected_property_count_);
object->set_elements(*elements);
object_ = object;
return;
}
// The map should have as many own descriptors as the number of properties
// we've created so far...
DCHECK_EQ(current_property_index_, map_->NumberOfOwnDescriptors());
// ... and all of those properties should be in-object data properties.
DCHECK_EQ(current_property_index_,
map_->GetInObjectProperties() - map_->UnusedInObjectProperties());
// Create a folded mutable HeapNumber allocation area before allocating the
// object -- this ensures that there is no allocation between the object
// allocation and its initial fields being initialised, where the verifier
// would see invalid double field state.
FoldedMutableHeapNumberAllocation hn_allocation(isolate_,
extra_heap_numbers_needed_);
// Allocate the object then immediately start a no_gc scope -- again, this
// is so the verifier doesn't see invalid double field state.
Handle<JSObject> object = isolate_->factory()->NewJSObjectFromMap(map_);
DisallowGarbageCollection no_gc;
Tagged<JSObject> raw_object = *object;
raw_object->set_elements(*elements);
Tagged<DescriptorArray> descriptors =
raw_object->map()->instance_descriptors();
WriteBarrierMode mode = raw_object->GetWriteBarrierMode(no_gc);
FoldedMutableHeapNumberAllocator hn_allocator(isolate_, &hn_allocation,
no_gc);
ReadOnlyRoots roots(isolate_);
// Initialize the in-object properties up to the last added property.
int current_property_offset = raw_object->GetInObjectPropertyOffset(0);
for (int i = 0; i < current_property_index_; ++i, ++value_it) {
InternalIndex descriptor_index(i);
Tagged<Object> value = **value_it;
// See comment in RegisterFieldNeedsFreshHeapNumber, we need to allocate
// HeapNumbers for double representation fields when we can't make
// existing HeapNumbers mutable, or when we only have a Smi value.
if (heap_number_mode_ != kHeapNumbersGuaranteedUniquelyOwned ||
IsSmi(value)) {
PropertyDetails details = descriptors->GetDetails(descriptor_index);
if (details.representation().IsDouble()) {
value = hn_allocator.AllocateNext(
roots, Float64(static_cast<double>(Cast<Smi>(value).value())));
}
}
DCHECK(FieldIndex::ForPropertyIndex(object->map(), i).is_inobject());
DCHECK_EQ(current_property_offset,
FieldIndex::ForPropertyIndex(object->map(), i).offset());
DCHECK_EQ(current_property_offset,
object->map()->GetInObjectPropertyOffset(i));
FieldIndex index = FieldIndex::ForInObjectOffset(current_property_offset,
FieldIndex::kTagged);
raw_object->RawFastInobjectPropertyAtPut(index, value, mode);
current_property_offset += kTaggedSize;
}
DCHECK_EQ(current_property_offset, object->map()->GetInObjectPropertyOffset(
current_property_index_));
object_ = object;
}
void AddSlowProperty(Handle<String> key, Handle<Object> value) {
DCHECK(!object_.is_null());
LookupIterator it(isolate_, object_, key, object_, LookupIterator::OWN);
JSObject::DefineOwnPropertyIgnoreAttributes(&it, value, NONE).Check();
}
Handle<JSObject> object() {
DCHECK(!object_.is_null());
return object_;
}
private:
template <typename GetKeyFunction>
V8_INLINE bool TryFastTransitionToPropertyKey(GetKeyFunction&& get_key,
Handle<String>* key_out) {
Handle<String> expected_key;
Handle<Map> target_map;
InternalIndex descriptor_index(current_property_index_);
if (IsOnExpectedFinalMapFastPath()) {
expected_key = handle(
Cast<String>(
expected_final_map_->instance_descriptors(isolate_)->GetKey(
descriptor_index)),
isolate_);
target_map = expected_final_map_;
} else {
TransitionsAccessor transitions(isolate_, *map_);
expected_key = transitions.ExpectedTransitionKey();
if (!expected_key.is_null()) {
// Directly read out the target while reading out the key, otherwise it
// might die if `get_key` can allocate.
target_map =
TransitionsAccessor(isolate_, *map_).ExpectedTransitionTarget();
}
}
Handle<String> key = *key_out = get_key(expected_key);
if (key.is_identical_to(expected_key)) {
// We were successful and we are done.
DCHECK_EQ(target_map->instance_descriptors()
->GetDetails(descriptor_index)
.location(),
PropertyLocation::kField);
map_ = target_map;
return true;
}
if (IsOnExpectedFinalMapFastPath()) {
// We were on the expected map fast path, but this missed that fast
// path, so rewind the optimistic setting of the current map and disable
// this fast path.
RewindExpectedFinalMapFastPathToBeforeCurrent();
property_count_in_expected_final_map_ = 0;
}
MaybeHandle<Map> maybe_target =
TransitionsAccessor(isolate_, *map_).FindTransitionToField(key);
if (!maybe_target.ToHandle(&target_map)) return false;
map_ = target_map;
return true;
}
V8_INLINE bool TryGeneralizeFieldToValue(DirectHandle<Object> value) {
DCHECK_LT(current_property_index_, map_->NumberOfOwnDescriptors());
InternalIndex descriptor_index(current_property_index_);
PropertyDetails current_details =
map_->instance_descriptors(isolate_)->GetDetails(descriptor_index);
Representation expected_representation = current_details.representation();
DCHECK_EQ(current_details.kind(), PropertyKind::kData);
DCHECK_EQ(current_details.location(), PropertyLocation::kField);
if (!Object::FitsRepresentation(*value, expected_representation)) {
Representation representation =
Object::OptimalRepresentation(*value, isolate_);
representation = representation.generalize(expected_representation);
if (!expected_representation.CanBeInPlaceChangedTo(representation)) {
// Reconfigure the map for the value, deprecating if necessary. This
// will only happen for double representation fields.
if (IsOnExpectedFinalMapFastPath()) {
// If we're on the fast path, we will have advanced the current map
// all the way to the final expected map. Make sure to rewind to the
// "real" current map if this happened.
//
// An alternative would be to deprecate the expected final map,
// migrate it to the new representation, and stay on the fast path.
// However, this would mean allocating all-new maps (with the new
// representation) all the way between the current map and the new
// expected final map; if we later fall off the fast path anyway, then
// all those newly allocated maps will end up unused.
RewindExpectedFinalMapFastPathToIncludeCurrent();
property_count_in_expected_final_map_ = 0;
}
MapUpdater mu(isolate_, map_);
Handle<Map> new_map = mu.ReconfigureToDataField(
descriptor_index, current_details.attributes(),
current_details.constness(), representation,
FieldType::Any(isolate_));
// We only want to stay on the fast path if we got a fast map.
if (new_map->is_dictionary_map()) return false;
map_ = new_map;
DCHECK(representation.IsDouble());
RegisterFieldNeedsFreshHeapNumber(value);
} else {
// Do the in-place reconfiguration.
DCHECK(!representation.IsDouble());
Handle<FieldType> value_type =
Object::OptimalType(*value, isolate_, representation);
MapUpdater::GeneralizeField(isolate_, map_, descriptor_index,
current_details.constness(), representation,
value_type);
}
} else if (expected_representation.IsHeapObject() &&
!FieldType::NowContains(
map_->instance_descriptors(isolate_)->GetFieldType(
descriptor_index),
value)) {
Handle<FieldType> value_type =
Object::OptimalType(*value, isolate_, expected_representation);
MapUpdater::GeneralizeField(isolate_, map_, descriptor_index,
current_details.constness(),
expected_representation, value_type);
} else if (expected_representation.IsDouble()) {
RegisterFieldNeedsFreshHeapNumber(value);
}
DCHECK(FieldType::NowContains(
map_->instance_descriptors(isolate_)->GetFieldType(descriptor_index),
value));
return true;
}
bool TryInitializeMapFromExpectedFinalMap() {
if (expected_final_map_.is_null()) return false;
if (expected_final_map_->elements_kind() != elements_kind_) return false;
int property_count_in_expected_final_map =
expected_final_map_->NumberOfOwnDescriptors();
if (property_count_in_expected_final_map < expected_property_count_)
return false;
map_ = expected_final_map_;
property_count_in_expected_final_map_ =
property_count_in_expected_final_map;
return true;
}
void InitializeMapFromZero() {
// Must be called before any properties are registered.
DCHECK_EQ(current_property_index_, 0);
map_ = isolate_->factory()->ObjectLiteralMapFromCache(
isolate_->native_context(), expected_property_count_);
if (elements_kind_ == DICTIONARY_ELEMENTS) {
map_ = Map::AsElementsKind(isolate_, map_, elements_kind_);
} else {
DCHECK_EQ(map_->elements_kind(), elements_kind_);
}
}
V8_INLINE bool IsOnExpectedFinalMapFastPath() const {
DCHECK_IMPLIES(property_count_in_expected_final_map_ > 0,
!expected_final_map_.is_null());
return current_property_index_ < property_count_in_expected_final_map_;
}
void RewindExpectedFinalMapFastPathToBeforeCurrent() {
DCHECK_GT(property_count_in_expected_final_map_, 0);
if (current_property_index_ == 0) {
InitializeMapFromZero();
DCHECK_EQ(0, map_->NumberOfOwnDescriptors());
}
if (current_property_index_ == 0) {
return;
}
DCHECK_EQ(*map_, *expected_final_map_);
map_ = handle(map_->FindFieldOwner(
isolate_, InternalIndex(current_property_index_ - 1)),
isolate_);
}
void RewindExpectedFinalMapFastPathToIncludeCurrent() {
DCHECK_EQ(*map_, *expected_final_map_);
map_ = handle(expected_final_map_->FindFieldOwner(
isolate_, InternalIndex(current_property_index_)),
isolate_);
}
V8_INLINE void RegisterFieldNeedsFreshHeapNumber(DirectHandle<Object> value) {
// We need to allocate a new HeapNumber for double representation fields if
// the HeapNumber values is not guaranteed to be uniquely owned by this
// object (and therefore can't be made mutable), or if the value is a Smi
// and there is no HeapNumber box for this value yet at all.
if (heap_number_mode_ == kHeapNumbersGuaranteedUniquelyOwned &&
!IsSmi(*value)) {
DCHECK(IsHeapNumber(*value));
return;
}
extra_heap_numbers_needed_++;
}
V8_INLINE void AdvanceToNextProperty() { current_property_index_++; }
Isolate* isolate_;
ElementsKind elements_kind_;
int expected_property_count_;
HeapNumberMode heap_number_mode_;
Handle<Map> map_;
int current_property_index_ = 0;
int extra_heap_numbers_needed_ = 0;
Handle<JSObject> object_;
Handle<Map> expected_final_map_ = {};
int property_count_in_expected_final_map_ = 0;
};
class NamedPropertyValueIterator {
public:
NamedPropertyValueIterator(const JsonProperty* it, const JsonProperty* end)
: it_(it), end_(end) {
DCHECK_LE(it_, end_);
DCHECK_IMPLIES(it_ != end_, !it_->string.is_index());
}
NamedPropertyValueIterator& operator++() {
DCHECK_LT(it_, end_);
do {
it_++;
} while (it_ != end_ && it_->string.is_index());
return *this;
}
Handle<Object> operator*() { return it_->value; }
bool operator!=(const NamedPropertyValueIterator& other) const {
return it_ != other.it_;
}
private:
// We need to store both the current iterator and the iterator end, since we
// don't want to iterate past the end on operator++ if the last property is an
// index property.
const JsonProperty* it_;
const JsonProperty* end_;
};
template <typename Char>
class JsonParser<Char>::NamedPropertyIterator {
public:
NamedPropertyIterator(JsonParser<Char>& parser, const JsonProperty* it,
const JsonProperty* end)
: parser_(parser), it_(it), end_(end) {
DCHECK_LE(it_, end_);
while (it_ != end_ && it_->string.is_index()) {
it_++;
}
start_ = it_;
}
void Advance() {
DCHECK_LT(it_, end_);
do {
it_++;
} while (it_ != end_ && it_->string.is_index());
}
bool Done() const {
DCHECK_LE(it_, end_);
return it_ == end_;
}
Handle<String> GetKey(Handle<String> expected_key_hint) {
return parser_.MakeString(it_->string, expected_key_hint);
}
Handle<Object> GetValue(bool will_revisit_value) {
// Revisiting values is free, so we don't need to cache the value anywhere.
return it_->value;
}
NamedPropertyValueIterator RevisitValues() {
return NamedPropertyValueIterator(start_, it_);
}
private:
JsonParser<Char>& parser_;
const JsonProperty* start_;
const JsonProperty* it_;
const JsonProperty* end_;
};
template <typename Char>
Handle<JSObject> JsonParser<Char>::BuildJsonObject(const JsonContinuation& cont,
Handle<Map> feedback) {
if (!feedback.is_null() && feedback->is_deprecated()) {
feedback = Map::Update(isolate_, feedback);
}
size_t start = cont.index;
DCHECK_LE(start, property_stack_.size());
int length = static_cast<int>(property_stack_.size() - start);
int named_length = length - cont.elements;
DCHECK_LE(0, named_length);
Handle<FixedArrayBase> elements;
ElementsKind elements_kind = HOLEY_ELEMENTS;
// First store the elements.
if (cont.elements > 0) {
// Store as dictionary elements if that would use less memory.
if (ShouldConvertToSlowElements(cont.elements, cont.max_index + 1)) {
Handle<NumberDictionary> elms =
NumberDictionary::New(isolate_, cont.elements);
for (int i = 0; i < length; i++) {
const JsonProperty& property = property_stack_[start + i];
if (!property.string.is_index()) continue;
uint32_t index = property.string.index();
Handle<Object> value = property.value;
NumberDictionary::UncheckedSet(isolate_, elms, index, value);
}
elms->SetInitialNumberOfElements(length);
elms->UpdateMaxNumberKey(cont.max_index, Handle<JSObject>::null());
elements_kind = DICTIONARY_ELEMENTS;
elements = elms;
} else {
Handle<FixedArray> elms =
factory()->NewFixedArrayWithHoles(cont.max_index + 1);
DisallowGarbageCollection no_gc;
Tagged<FixedArray> raw_elements = *elms;
WriteBarrierMode mode = raw_elements->GetWriteBarrierMode(no_gc);
for (int i = 0; i < length; i++) {
const JsonProperty& property = property_stack_[start + i];
if (!property.string.is_index()) continue;
uint32_t index = property.string.index();
DirectHandle<Object> value = property.value;
raw_elements->set(static_cast<int>(index), *value, mode);
}
elements = elms;
}
} else {
elements = factory()->empty_fixed_array();
}
JSDataObjectBuilder js_data_object_builder(
isolate_, elements_kind, named_length, feedback,
JSDataObjectBuilder::kHeapNumbersGuaranteedUniquelyOwned);
NamedPropertyIterator it(*this, property_stack_.begin() + start,
property_stack_.end());
return js_data_object_builder.BuildFromIterator(it, elements);
}
template <typename Char>
Handle<Object> JsonParser<Char>::BuildJsonArray(size_t start) {
int length = static_cast<int>(element_stack_.size() - start);
ElementsKind kind = PACKED_SMI_ELEMENTS;
for (size_t i = start; i < element_stack_.size(); i++) {
Tagged<Object> value = *element_stack_[i];
if (IsHeapObject(value)) {
if (IsHeapNumber(Cast<HeapObject>(value))) {
kind = PACKED_DOUBLE_ELEMENTS;
} else {
kind = PACKED_ELEMENTS;
break;
}
}
}
Handle<JSArray> array = factory()->NewJSArray(kind, length, length);
if (kind == PACKED_DOUBLE_ELEMENTS) {
DisallowGarbageCollection no_gc;
Tagged<FixedDoubleArray> elements =
Cast<FixedDoubleArray>(array->elements());
for (int i = 0; i < length; i++) {
elements->set(i, Object::NumberValue(*element_stack_[start + i]));
}
} else {
DisallowGarbageCollection no_gc;
Tagged<FixedArray> elements = Cast<FixedArray>(array->elements());
WriteBarrierMode mode = kind == PACKED_SMI_ELEMENTS
? SKIP_WRITE_BARRIER
: elements->GetWriteBarrierMode(no_gc);
for (int i = 0; i < length; i++) {
elements->set(i, *element_stack_[start + i], mode);
}
}
return array;
}
// Parse rawJSON value.
template <typename Char>
bool JsonParser<Char>::ParseRawJson() {
if (end_ == cursor_) {
isolate_->Throw(*isolate_->factory()->NewSyntaxError(
MessageTemplate::kInvalidRawJsonValue));
return false;
}
next_ = GetTokenForCharacter(*cursor_);
switch (peek()) {
case JsonToken::STRING:
Consume(JsonToken::STRING);
ScanJsonString(false);
break;
case JsonToken::NUMBER:
ParseJsonNumber();
break;
case JsonToken::TRUE_LITERAL:
ScanLiteral("true");
break;
case JsonToken::FALSE_LITERAL:
ScanLiteral("false");
break;
case JsonToken::NULL_LITERAL:
ScanLiteral("null");
break;
default:
ReportUnexpectedCharacter(CurrentCharacter());
return false;
}
if (isolate_->has_exception()) return false;
if (cursor_ != end_) {
isolate_->Throw(*isolate_->factory()->NewSyntaxError(
MessageTemplate::kInvalidRawJsonValue));
return false;
}
return true;
}
template <typename Char>
V8_INLINE MaybeHandle<Object> JsonParser<Char>::ParseJsonValueRecursive(
Handle<Map> feedback) {
SkipWhitespace();
switch (peek()) {
case JsonToken::NUMBER:
return ParseJsonNumber();
case JsonToken::STRING:
Consume(JsonToken::STRING);
return MakeString(ScanJsonString(false));
case JsonToken::TRUE_LITERAL:
ScanLiteral("true");
return factory()->true_value();
case JsonToken::FALSE_LITERAL:
ScanLiteral("false");
return factory()->false_value();
case JsonToken::NULL_LITERAL:
ScanLiteral("null");
return factory()->null_value();
case JsonToken::LBRACE:
return ParseJsonObject(feedback);
case JsonToken::LBRACK:
return ParseJsonArray();
case JsonToken::COLON:
case JsonToken::COMMA:
case JsonToken::ILLEGAL:
case JsonToken::RBRACE:
case JsonToken::RBRACK:
case JsonToken::EOS:
ReportUnexpectedCharacter(CurrentCharacter());
// Pop the continuation stack to correctly tear down handle scopes.
return MaybeHandle<Object>();
case JsonToken::WHITESPACE:
UNREACHABLE();
}
}
template <typename Char>
MaybeHandle<Object> JsonParser<Char>::ParseJsonObject(Handle<Map> feedback) {
{
StackLimitCheck check(isolate_);
if (V8_UNLIKELY(check.HasOverflowed())) {
return ParseJsonValue<false>();
}
}
Consume(JsonToken::LBRACE);
if (Check(JsonToken::RBRACE)) {
return factory()->NewJSObject(object_constructor_);
}
JsonContinuation cont(isolate_, JsonContinuation::kObjectProperty,
property_stack_.size());
bool first = true;
do {
ExpectNext(
JsonToken::STRING,
first ? MessageTemplate::kJsonParseExpectedPropNameOrRBrace
: MessageTemplate::kJsonParseExpectedDoubleQuotedPropertyName);
JsonString key = ScanJsonPropertyKey(&cont);
ExpectNext(JsonToken::COLON,
MessageTemplate::kJsonParseExpectedColonAfterPropertyName);
Handle<Object> value;
if (V8_UNLIKELY(!ParseJsonValueRecursive().ToHandle(&value))) return {};
property_stack_.emplace_back(key, value);
first = false;
} while (Check(JsonToken::COMMA));
Expect(JsonToken::RBRACE, MessageTemplate::kJsonParseExpectedCommaOrRBrace);
Handle<Object> result = BuildJsonObject(cont, feedback);
property_stack_.resize_no_init(cont.index);
return cont.scope.CloseAndEscape(result);
}
template <typename Char>
MaybeHandle<Object> JsonParser<Char>::ParseJsonArray() {
{
StackLimitCheck check(isolate_);
if (V8_UNLIKELY(check.HasOverflowed())) {
return ParseJsonValue<false>();
}
}
Consume(JsonToken::LBRACK);
if (Check(JsonToken::RBRACK)) {
return factory()->NewJSArray(0, PACKED_SMI_ELEMENTS);
}
HandleScope handle_scope(isolate_);
size_t start = element_stack_.size();
Handle<Object> value;
if (V8_UNLIKELY(!ParseJsonValueRecursive().ToHandle(&value))) return {};
element_stack_.emplace_back(value);
while (Check(JsonToken::COMMA)) {
Handle<Map> feedback;
if (IsJSObject(*value)) {
Tagged<Map> maybe_feedback = Cast<JSObject>(*value)->map();
// Don't consume feedback from objects with a map that's detached
// from the transition tree.
if (!maybe_feedback->IsDetached(isolate_)) {
feedback = handle(maybe_feedback, isolate_);
}
}
if (V8_UNLIKELY(!ParseJsonValueRecursive(feedback).ToHandle(&value))) {
return {};
}
element_stack_.emplace_back(value);
}
Expect(JsonToken::RBRACK, MessageTemplate::kJsonParseExpectedCommaOrRBrack);
Handle<Object> result = BuildJsonArray(start);
element_stack_.resize_no_init(start);
return handle_scope.CloseAndEscape(result);
}
// Parse any JSON value.
template <typename Char>
template <bool should_track_json_source>
MaybeHandle<Object> JsonParser<Char>::ParseJsonValue() {
std::vector<JsonContinuation> cont_stack;
cont_stack.reserve(16);
JsonContinuation cont(isolate_, JsonContinuation::kReturn, 0);
Handle<Object> value;
// When should_track_json_source is true, we use val_node to record current
// JSON value's parse node.
//
// For primitive values, the val_node is the source string of the JSON value.
//
// For JSObject values, the val_node is an ObjectHashTable in which the key is
// the property name and the first value is the property value's parse
// node. The order in which properties are defined may be different from the
// order in which properties are enumerated when calling
// InternalizeJSONProperty for the JSObject value. E.g., the JSON source
// string is '{"a": 1, "1": 2}', and the properties enumerate order is ["1",
// "a"]. Moreover, properties may be defined repeatedly in the JSON string.
// E.g., the JSON string is '{"a": 1, "a": 1}', and the properties enumerate
// order is ["a"]. So we cannot use the FixedArray to record the properties's
// parse node by the order in which properties are defined and we use a
// ObjectHashTable here to record the property name and the property's parse
// node. We then look up the property's parse node by the property name when
// calling InternalizeJSONProperty. The second value associated with the key
// is the property value's snapshot.
//
// For JSArray values, the val_node is a FixedArray containing the parse nodes
// and snapshots of the elements.
//
// For information about snapshotting, see below.
Handle<Object> val_node;
// Record the start position and end position for the primitive values.
int start_position;
int end_position;
// Workaround for -Wunused-but-set-variable on old gcc versions (version < 8).
USE(start_position);
USE(end_position);
// element_val_node_stack is used to track all the elements's
// parse nodes. And we use this to construct the JSArray's
// parse node and value snapshot.
SmallVector<Handle<Object>> element_val_node_stack;
// property_val_node_stack is used to track all the property
// value's parse nodes. And we use this to construct the
// JSObject's parse node and value snapshot.
SmallVector<Handle<Object>> property_val_node_stack;
while (true) {
// Produce a json value.
//
// Iterate until a value is produced. Starting but not immediately finishing
// objects and arrays will cause the loop to continue until a first member
// is completed.
while (true) {
SkipWhitespace();
// The switch is immediately followed by 'break' so we can use 'break' to
// break out of the loop, and 'continue' to continue the loop.
if constexpr (should_track_json_source) {
start_position = position();
}
switch (peek()) {
case JsonToken::STRING:
Consume(JsonToken::STRING);
value = MakeString(ScanJsonString(false));
if constexpr (should_track_json_source) {
end_position = position();
val_node = isolate_->factory()->NewSubString(
source_, start_position, end_position);
}
break;
case JsonToken::NUMBER:
value = ParseJsonNumber();
if constexpr (should_track_json_source) {
end_position = position();
val_node = isolate_->factory()->NewSubString(
source_, start_position, end_position);
}
break;
case JsonToken::LBRACE: {
Consume(JsonToken::LBRACE);
if (Check(JsonToken::RBRACE)) {
// TODO(verwaest): Directly use the map instead.
value = factory()->NewJSObject(object_constructor_);
if constexpr (should_track_json_source) {
val_node = ObjectTwoHashTable::New(isolate_, 0);
}
break;
}
// Start parsing an object with properties.
cont_stack.emplace_back(std::move(cont));
cont = JsonContinuation(isolate_, JsonContinuation::kObjectProperty,
property_stack_.size());
// Parse the property key.
ExpectNext(JsonToken::STRING,
MessageTemplate::kJsonParseExpectedPropNameOrRBrace);
property_stack_.emplace_back(ScanJsonPropertyKey(&cont));
if constexpr (should_track_json_source) {
property_val_node_stack.emplace_back(Handle<Object>());
}
ExpectNext(JsonToken::COLON,
MessageTemplate::kJsonParseExpectedColonAfterPropertyName);
// Continue to start producing the first property value.
continue;
}
case JsonToken::LBRACK:
Consume(JsonToken::LBRACK);
if (Check(JsonToken::RBRACK)) {
value = factory()->NewJSArray(0, PACKED_SMI_ELEMENTS);
if constexpr (should_track_json_source) {
val_node = factory()->NewFixedArray(0);
}
break;
}
// Start parsing an array with elements.
cont_stack.emplace_back(std::move(cont));
cont = JsonContinuation(isolate_, JsonContinuation::kArrayElement,
element_stack_.size());
// Continue to start producing the first array element.
continue;
case JsonToken::TRUE_LITERAL:
ScanLiteral("true");
value = factory()->true_value();
if constexpr (should_track_json_source) {
val_node = isolate_->factory()->true_string();
}
break;
case JsonToken::FALSE_LITERAL:
ScanLiteral("false");
value = factory()->false_value();
if constexpr (should_track_json_source) {
val_node = isolate_->factory()->false_string();
}
break;
case JsonToken::NULL_LITERAL:
ScanLiteral("null");
value = factory()->null_value();
if constexpr (should_track_json_source) {
val_node = isolate_->factory()->null_string();
}
break;
case JsonToken::COLON:
case JsonToken::COMMA:
case JsonToken::ILLEGAL:
case JsonToken::RBRACE:
case JsonToken::RBRACK:
case JsonToken::EOS:
ReportUnexpectedCharacter(CurrentCharacter());
// Pop the continuation stack to correctly tear down handle scopes.
while (!cont_stack.empty()) {
cont = std::move(cont_stack.back());
cont_stack.pop_back();
}
return MaybeHandle<Object>();
case JsonToken::WHITESPACE:
UNREACHABLE();
}
// Done producing a value, consume it.
break;
}
// Consume a produced json value.
//
// Iterate as long as values are produced (arrays or object literals are
// finished).
while (true) {
// The switch is immediately followed by 'break' so we can use 'break' to
// break out of the loop, and 'continue' to continue the loop.
switch (cont.type()) {
case JsonContinuation::kReturn:
if constexpr (should_track_json_source) {
DCHECK(!val_node.is_null());
Tagged<Object> raw_value = *value;
parsed_val_node_ = cont.scope.CloseAndEscape(val_node);
return cont.scope.CloseAndEscape(handle(raw_value, isolate_));
} else {
return cont.scope.CloseAndEscape(value);
}
case JsonContinuation::kObjectProperty: {
// Store the previous property value into its property info.
property_stack_.back().value = value;
if constexpr (should_track_json_source) {
property_val_node_stack.back() = val_node;
}
if (V8_LIKELY(Check(JsonToken::COMMA))) {
// Parse the property key.
ExpectNext(
JsonToken::STRING,
MessageTemplate::kJsonParseExpectedDoubleQuotedPropertyName);
property_stack_.emplace_back(ScanJsonPropertyKey(&cont));
if constexpr (should_track_json_source) {
property_val_node_stack.emplace_back(Handle<Object>());
}
ExpectNext(
JsonToken::COLON,
MessageTemplate::kJsonParseExpectedColonAfterPropertyName);
// Break to start producing the subsequent property value.
break;
}
Handle<Map> feedback;
if (cont_stack.size() > 0 &&
cont_stack.back().type() == JsonContinuation::kArrayElement &&
cont_stack.back().index < element_stack_.size() &&
IsJSObject(*element_stack_.back())) {
Tagged<Map> maybe_feedback =
Cast<JSObject>(*element_stack_.back())->map();
// Don't consume feedback from objects with a map that's detached
// from the transition tree.
if (!maybe_feedback->IsDetached(isolate_)) {
feedback = handle(maybe_feedback, isolate_);
}
}
value = BuildJsonObject(cont, feedback);
Expect(JsonToken::RBRACE,
MessageTemplate::kJsonParseExpectedCommaOrRBrace);
// Return the object.
if constexpr (should_track_json_source) {
size_t start = cont.index;
int num_properties =
static_cast<int>(property_stack_.size() - start);
Handle<ObjectTwoHashTable> table =
ObjectTwoHashTable::New(isolate(), num_properties);
for (int i = 0; i < num_properties; i++) {
const JsonProperty& property = property_stack_[start + i];
Handle<Object> property_val_node =
property_val_node_stack[start + i];
Handle<Object> property_snapshot = property.value;
Handle<String> key;
if (property.string.is_index()) {
key = factory()->Uint32ToString(property.string.index());
} else {
key = MakeString(property.string);
}
table = ObjectTwoHashTable::Put(
isolate(), table, key,
{property_val_node, property_snapshot});
}
property_val_node_stack.resize_no_init(cont.index);
DisallowGarbageCollection no_gc;
Tagged<ObjectTwoHashTable> raw_table = *table;
value = cont.scope.CloseAndEscape(value);
val_node = cont.scope.CloseAndEscape(handle(raw_table, isolate_));
} else {
value = cont.scope.CloseAndEscape(value);
}
property_stack_.resize_no_init(cont.index);
// Pop the continuation.
cont = std::move(cont_stack.back());
cont_stack.pop_back();
// Consume to produced object.
continue;
}
case JsonContinuation::kArrayElement: {
// Store the previous element on the stack.
element_stack_.emplace_back(value);
if constexpr (should_track_json_source) {
element_val_node_stack.emplace_back(val_node);
}
// Break to start producing the subsequent element value.
if (V8_LIKELY(Check(JsonToken::COMMA))) break;
value = BuildJsonArray(cont.index);
Expect(JsonToken::RBRACK,
MessageTemplate::kJsonParseExpectedCommaOrRBrack);
// Return the array.
if constexpr (should_track_json_source) {
size_t start = cont.index;
int num_elements = static_cast<int>(element_stack_.size() - start);
DirectHandle<FixedArray> val_node_and_snapshot_array =
factory()->NewFixedArray(num_elements * 2);
DisallowGarbageCollection no_gc;
Tagged<FixedArray> raw_val_node_and_snapshot_array =
*val_node_and_snapshot_array;
for (int i = 0; i < num_elements; i++) {
raw_val_node_and_snapshot_array->set(
i * 2, *element_val_node_stack[start + i]);
raw_val_node_and_snapshot_array->set(i * 2 + 1,
*element_stack_[start + i]);
}
element_val_node_stack.resize_no_init(cont.index);
value = cont.scope.CloseAndEscape(value);
val_node = cont.scope.CloseAndEscape(
handle(raw_val_node_and_snapshot_array, isolate_));
} else {
value = cont.scope.CloseAndEscape(value);
}
element_stack_.resize_no_init(cont.index);
// Pop the continuation.
cont = std::move(cont_stack.back());
cont_stack.pop_back();
// Consume the produced array.
continue;
}
}
// Done consuming a value. Produce next value.
break;
}
}
}
template <typename Char>
void JsonParser<Char>::AdvanceToNonDecimal() {
cursor_ =
std::find_if(cursor_, end_, [](Char c) { return !IsDecimalDigit(c); });
}
template <typename Char>
Handle<Object> JsonParser<Char>::ParseJsonNumber() {
double number;
int sign = 1;
{
const Char* start = cursor_;
DisallowGarbageCollection no_gc;
base::uc32 c = *cursor_;
if (c == '-') {
sign = -1;
c = NextCharacter();
}
if (c == '0') {
// Prefix zero is only allowed if it's the only digit before
// a decimal point or exponent.
c = NextCharacter();
if (base::IsInRange(c, 0,
static_cast<int32_t>(unibrow::Latin1::kMaxChar)) &&
IsNumberPart(character_json_scan_flags[c])) {
if (V8_UNLIKELY(IsDecimalDigit(c))) {
AllowGarbageCollection allow_before_exception;
ReportUnexpectedToken(JsonToken::NUMBER);
return handle(Smi::FromInt(0), isolate_);
}
} else if (sign > 0) {
return handle(Smi::FromInt(0), isolate_);
}
} else {
const Char* smi_start = cursor_;
static_assert(Smi::IsValid(-999999999));
static_assert(Smi::IsValid(999999999));
const int kMaxSmiLength = 9;
int32_t i = 0;
const Char* stop = cursor_ + kMaxSmiLength;
if (stop > end_) stop = end_;
while (cursor_ < stop && IsDecimalDigit(*cursor_)) {
i = (i * 10) + ((*cursor_) - '0');
cursor_++;
}
if (V8_UNLIKELY(smi_start == cursor_)) {
AllowGarbageCollection allow_before_exception;
ReportUnexpectedToken(
JsonToken::ILLEGAL,
MessageTemplate::kJsonParseNoNumberAfterMinusSign);
return handle(Smi::FromInt(0), isolate_);
}
c = CurrentCharacter();
if (!base::IsInRange(c, 0,
static_cast<int32_t>(unibrow::Latin1::kMaxChar)) ||
!IsNumberPart(character_json_scan_flags[c])) {
// Smi.
// TODO(verwaest): Cache?
return handle(Smi::FromInt(i * sign), isolate_);
}
AdvanceToNonDecimal();
}
if (CurrentCharacter() == '.') {
c = NextCharacter();
if (!IsDecimalDigit(c)) {
AllowGarbageCollection allow_before_exception;
ReportUnexpectedToken(
JsonToken::ILLEGAL,
MessageTemplate::kJsonParseUnterminatedFractionalNumber);
return handle(Smi::FromInt(0), isolate_);
}
AdvanceToNonDecimal();
}
if (AsciiAlphaToLower(CurrentCharacter()) == 'e') {
c = NextCharacter();
if (c == '-' || c == '+') c = NextCharacter();
if (!IsDecimalDigit(c)) {
AllowGarbageCollection allow_before_exception;
ReportUnexpectedToken(
JsonToken::ILLEGAL,
MessageTemplate::kJsonParseExponentPartMissingNumber);
return handle(Smi::FromInt(0), isolate_);
}
AdvanceToNonDecimal();
}
base::Vector<const Char> chars(start, cursor_ - start);
number =
StringToDouble(chars,
NO_CONVERSION_FLAGS, // Hex, octal or trailing junk.
std::numeric_limits<double>::quiet_NaN());
DCHECK(!std::isnan(number));
}
return factory()->NewNumber(number);
}
namespace {
template <typename Char>
bool Matches(base::Vector<const Char> chars, Handle<String> string) {
DCHECK(!string.is_null());
return string->IsEqualTo(chars);
}
} // namespace
template <typename Char>
template <typename SinkSeqString>
Handle<String> JsonParser<Char>::DecodeString(
const JsonString& string, Handle<SinkSeqString> intermediate,
Handle<String> hint) {
using SinkChar = typename SinkSeqString::Char;
{
DisallowGarbageCollection no_gc;
SinkChar* dest = intermediate->GetChars(no_gc);
if (!string.has_escape()) {
DCHECK(!string.internalize());
CopyChars(dest, chars_ + string.start(), string.length());
return intermediate;
}
DecodeString(dest, string.start(), string.length());
if (!string.internalize()) return intermediate;
base::Vector<const SinkChar> data(dest, string.length());
if (!hint.is_null() && Matches(data, hint)) return hint;
}
return factory()->InternalizeString(intermediate, 0, string.length());
}
template <typename Char>
Handle<String> JsonParser<Char>::MakeString(const JsonString& string,
Handle<String> hint) {
if (string.length() == 0) return factory()->empty_string();
if (string.internalize() && !string.has_escape()) {
if (!hint.is_null()) {
base::Vector<const Char> data(chars_ + string.start(), string.length());
if (Matches(data, hint)) return hint;
}
if (chars_may_relocate_) {
return factory()->InternalizeString(Cast<SeqString>(source_),
string.start(), string.length(),
string.needs_conversion());
}
base::Vector<const Char> chars(chars_ + string.start(), string.length());
return factory()->InternalizeString(chars, string.needs_conversion());
}
if (sizeof(Char) == 1 ? V8_LIKELY(!string.needs_conversion())
: string.needs_conversion()) {
Handle<SeqOneByteString> intermediate =
factory()->NewRawOneByteString(string.length()).ToHandleChecked();
return DecodeString(string, intermediate, hint);
}
Handle<SeqTwoByteString> intermediate =
factory()->NewRawTwoByteString(string.length()).ToHandleChecked();
return DecodeString(string, intermediate, hint);
}
template <typename Char>
template <typename SinkChar>
void JsonParser<Char>::DecodeString(SinkChar* sink, int start, int length) {
SinkChar* sink_start = sink;
const Char* cursor = chars_ + start;
while (true) {
const Char* end = cursor + length - (sink - sink_start);
cursor = std::find_if(cursor, end, [&sink](Char c) {
if (c == '\\') return true;
*sink++ = c;
return false;
});
if (cursor == end) return;
cursor++;
switch (GetEscapeKind(character_json_scan_flags[*cursor])) {
case EscapeKind::kSelf:
*sink++ = *cursor;
break;
case EscapeKind::kBackspace:
*sink++ = '\x08';
break;
case EscapeKind::kTab:
*sink++ = '\x09';
break;
case EscapeKind::kNewLine:
*sink++ = '\x0A';
break;
case EscapeKind::kFormFeed:
*sink++ = '\x0C';
break;
case EscapeKind::kCarriageReturn:
*sink++ = '\x0D';
break;
case EscapeKind::kUnicode: {
base::uc32 value = 0;
for (int i = 0; i < 4; i++) {
value = value * 16 + base::HexValue(*++cursor);
}
if (value <=
static_cast<base::uc32>(unibrow::Utf16::kMaxNonSurrogateCharCode)) {
*sink++ = value;
} else {
*sink++ = unibrow::Utf16::LeadSurrogate(value);
*sink++ = unibrow::Utf16::TrailSurrogate(value);
}
break;
}
case EscapeKind::kIllegal:
UNREACHABLE();
}
cursor++;
}
}
template <typename Char>
JsonString JsonParser<Char>::ScanJsonString(bool needs_internalization) {
DisallowGarbageCollection no_gc;
int start = position();
int offset = start;
bool has_escape = false;
base::uc32 bits = 0;
while (true) {
cursor_ = std::find_if(cursor_, end_, [&bits](Char c) {
if (sizeof(Char) == 2 && V8_UNLIKELY(c > unibrow::Latin1::kMaxChar)) {
bits |= c;
return false;
}
return MayTerminateJsonString(character_json_scan_flags[c]);
});
if (V8_UNLIKELY(is_at_end())) {
AllowGarbageCollection allow_before_exception;
ReportUnexpectedToken(JsonToken::ILLEGAL,
MessageTemplate::kJsonParseUnterminatedString);
break;
}
if (*cursor_ == '"') {
int end = position();
advance();
int length = end - offset;
bool convert = sizeof(Char) == 1 ? bits > unibrow::Latin1::kMaxChar
: bits <= unibrow::Latin1::kMaxChar;
constexpr int kMaxInternalizedStringValueLength = 10;
bool internalize =
needs_internalization ||
(sizeof(Char) == 1 && length < kMaxInternalizedStringValueLength);
return JsonString(start, length, convert, internalize, has_escape);
}
if (*cursor_ == '\\') {
has_escape = true;
base::uc32 c = NextCharacter();
if (V8_UNLIKELY(!base::IsInRange(
c, 0, static_cast<int32_t>(unibrow::Latin1::kMaxChar)))) {
AllowGarbageCollection allow_before_exception;
ReportUnexpectedCharacter(c);
break;
}
switch (GetEscapeKind(character_json_scan_flags[c])) {
case EscapeKind::kSelf:
case EscapeKind::kBackspace:
case EscapeKind::kTab:
case EscapeKind::kNewLine:
case EscapeKind::kFormFeed:
case EscapeKind::kCarriageReturn:
offset += 1;
break;
case EscapeKind::kUnicode: {
base::uc32 value = ScanUnicodeCharacter();
if (value == kInvalidUnicodeCharacter) {
AllowGarbageCollection allow_before_exception;
ReportUnexpectedToken(JsonToken::ILLEGAL,
MessageTemplate::kJsonParseBadUnicodeEscape);
return JsonString();
}
bits |= value;
// \uXXXX results in either 1 or 2 Utf16 characters, depending on
// whether the decoded value requires a surrogate pair.
offset += 5 - (value > static_cast<base::uc32>(
unibrow::Utf16::kMaxNonSurrogateCharCode));
break;
}
case EscapeKind::kIllegal:
AllowGarbageCollection allow_before_exception;
ReportUnexpectedToken(JsonToken::ILLEGAL,
MessageTemplate::kJsonParseBadEscapedCharacter);
return JsonString();
}
advance();
continue;
}
DCHECK_LT(*cursor_, 0x20);
AllowGarbageCollection allow_before_exception;
ReportUnexpectedToken(JsonToken::ILLEGAL,
MessageTemplate::kJsonParseBadControlCharacter);
break;
}
return JsonString();
}
// Explicit instantiation.
template class JsonParser<uint8_t>;
template class JsonParser<uint16_t>;
} // namespace internal
} // namespace v8