| // 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-stringifier.h" | |
| #include "src/conversions.h" | |
| #include "src/heap/heap-inl.h" | |
| #include "src/lookup.h" | |
| #include "src/messages.h" | |
| #include "src/objects-inl.h" | |
| #include "src/objects/js-array-inl.h" | |
| #include "src/string-builder-inl.h" | |
| #include "src/utils.h" | |
| namespace v8 { | |
| namespace internal { | |
| class JsonStringifier BASE_EMBEDDED { | |
| public: | |
| explicit JsonStringifier(Isolate* isolate); | |
| ~JsonStringifier() { DeleteArray(gap_); } | |
| V8_WARN_UNUSED_RESULT MaybeHandle<Object> Stringify(Handle<Object> object, | |
| Handle<Object> replacer, | |
| Handle<Object> gap); | |
| private: | |
| enum Result { UNCHANGED, SUCCESS, EXCEPTION }; | |
| bool InitializeReplacer(Handle<Object> replacer); | |
| bool InitializeGap(Handle<Object> gap); | |
| V8_WARN_UNUSED_RESULT MaybeHandle<Object> ApplyToJsonFunction( | |
| Handle<Object> object, Handle<Object> key); | |
| V8_WARN_UNUSED_RESULT MaybeHandle<Object> ApplyReplacerFunction( | |
| Handle<Object> value, Handle<Object> key, Handle<Object> initial_holder); | |
| // Entry point to serialize the object. | |
| V8_INLINE Result SerializeObject(Handle<Object> obj) { | |
| return Serialize_<false>(obj, false, factory()->empty_string()); | |
| } | |
| // Serialize an array element. | |
| // The index may serve as argument for the toJSON function. | |
| V8_INLINE Result SerializeElement(Isolate* isolate, Handle<Object> object, | |
| int i) { | |
| return Serialize_<false>(object, false, | |
| Handle<Object>(Smi::FromInt(i), isolate)); | |
| } | |
| // Serialize a object property. | |
| // The key may or may not be serialized depending on the property. | |
| // The key may also serve as argument for the toJSON function. | |
| V8_INLINE Result SerializeProperty(Handle<Object> object, bool deferred_comma, | |
| Handle<String> deferred_key) { | |
| DCHECK(!deferred_key.is_null()); | |
| return Serialize_<true>(object, deferred_comma, deferred_key); | |
| } | |
| template <bool deferred_string_key> | |
| Result Serialize_(Handle<Object> object, bool comma, Handle<Object> key); | |
| V8_INLINE void SerializeDeferredKey(bool deferred_comma, | |
| Handle<Object> deferred_key); | |
| Result SerializeSmi(Smi* object); | |
| Result SerializeDouble(double number); | |
| V8_INLINE Result SerializeHeapNumber(Handle<HeapNumber> object) { | |
| return SerializeDouble(object->value()); | |
| } | |
| Result SerializeJSValue(Handle<JSValue> object); | |
| V8_INLINE Result SerializeJSArray(Handle<JSArray> object); | |
| V8_INLINE Result SerializeJSObject(Handle<JSObject> object); | |
| Result SerializeJSProxy(Handle<JSProxy> object); | |
| Result SerializeJSReceiverSlow(Handle<JSReceiver> object); | |
| Result SerializeArrayLikeSlow(Handle<JSReceiver> object, uint32_t start, | |
| uint32_t length); | |
| void SerializeString(Handle<String> object); | |
| template <typename SrcChar, typename DestChar> | |
| V8_INLINE static void SerializeStringUnchecked_( | |
| Vector<const SrcChar> src, | |
| IncrementalStringBuilder::NoExtend<DestChar>* dest); | |
| template <typename SrcChar, typename DestChar> | |
| V8_INLINE void SerializeString_(Handle<String> string); | |
| template <typename Char> | |
| V8_INLINE static bool DoNotEscape(Char c); | |
| V8_INLINE void NewLine(); | |
| V8_INLINE void Indent() { indent_++; } | |
| V8_INLINE void Unindent() { indent_--; } | |
| V8_INLINE void Separator(bool first); | |
| Handle<JSReceiver> CurrentHolder(Handle<Object> value, | |
| Handle<Object> inital_holder); | |
| Result StackPush(Handle<Object> object); | |
| void StackPop(); | |
| Factory* factory() { return isolate_->factory(); } | |
| Isolate* isolate_; | |
| IncrementalStringBuilder builder_; | |
| Handle<String> tojson_string_; | |
| Handle<JSArray> stack_; | |
| Handle<FixedArray> property_list_; | |
| Handle<JSReceiver> replacer_function_; | |
| uc16* gap_; | |
| int indent_; | |
| static const int kJsonEscapeTableEntrySize = 8; | |
| static const char* const JsonEscapeTable; | |
| }; | |
| MaybeHandle<Object> JsonStringify(Isolate* isolate, Handle<Object> object, | |
| Handle<Object> replacer, Handle<Object> gap) { | |
| JsonStringifier stringifier(isolate); | |
| return stringifier.Stringify(object, replacer, gap); | |
| } | |
| // Translation table to escape Latin1 characters. | |
| // Table entries start at a multiple of 8 and are null-terminated. | |
| const char* const JsonStringifier::JsonEscapeTable = | |
| "\\u0000\0 \\u0001\0 \\u0002\0 \\u0003\0 " | |
| "\\u0004\0 \\u0005\0 \\u0006\0 \\u0007\0 " | |
| "\\b\0 \\t\0 \\n\0 \\u000b\0 " | |
| "\\f\0 \\r\0 \\u000e\0 \\u000f\0 " | |
| "\\u0010\0 \\u0011\0 \\u0012\0 \\u0013\0 " | |
| "\\u0014\0 \\u0015\0 \\u0016\0 \\u0017\0 " | |
| "\\u0018\0 \\u0019\0 \\u001a\0 \\u001b\0 " | |
| "\\u001c\0 \\u001d\0 \\u001e\0 \\u001f\0 " | |
| " \0 !\0 \\\"\0 #\0 " | |
| "$\0 %\0 &\0 '\0 " | |
| "(\0 )\0 *\0 +\0 " | |
| ",\0 -\0 .\0 /\0 " | |
| "0\0 1\0 2\0 3\0 " | |
| "4\0 5\0 6\0 7\0 " | |
| "8\0 9\0 :\0 ;\0 " | |
| "<\0 =\0 >\0 ?\0 " | |
| "@\0 A\0 B\0 C\0 " | |
| "D\0 E\0 F\0 G\0 " | |
| "H\0 I\0 J\0 K\0 " | |
| "L\0 M\0 N\0 O\0 " | |
| "P\0 Q\0 R\0 S\0 " | |
| "T\0 U\0 V\0 W\0 " | |
| "X\0 Y\0 Z\0 [\0 " | |
| "\\\\\0 ]\0 ^\0 _\0 " | |
| "`\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 \x7F\0 " | |
| "\x80\0 \x81\0 \x82\0 \x83\0 " | |
| "\x84\0 \x85\0 \x86\0 \x87\0 " | |
| "\x88\0 \x89\0 \x8A\0 \x8B\0 " | |
| "\x8C\0 \x8D\0 \x8E\0 \x8F\0 " | |
| "\x90\0 \x91\0 \x92\0 \x93\0 " | |
| "\x94\0 \x95\0 \x96\0 \x97\0 " | |
| "\x98\0 \x99\0 \x9A\0 \x9B\0 " | |
| "\x9C\0 \x9D\0 \x9E\0 \x9F\0 " | |
| "\xA0\0 \xA1\0 \xA2\0 \xA3\0 " | |
| "\xA4\0 \xA5\0 \xA6\0 \xA7\0 " | |
| "\xA8\0 \xA9\0 \xAA\0 \xAB\0 " | |
| "\xAC\0 \xAD\0 \xAE\0 \xAF\0 " | |
| "\xB0\0 \xB1\0 \xB2\0 \xB3\0 " | |
| "\xB4\0 \xB5\0 \xB6\0 \xB7\0 " | |
| "\xB8\0 \xB9\0 \xBA\0 \xBB\0 " | |
| "\xBC\0 \xBD\0 \xBE\0 \xBF\0 " | |
| "\xC0\0 \xC1\0 \xC2\0 \xC3\0 " | |
| "\xC4\0 \xC5\0 \xC6\0 \xC7\0 " | |
| "\xC8\0 \xC9\0 \xCA\0 \xCB\0 " | |
| "\xCC\0 \xCD\0 \xCE\0 \xCF\0 " | |
| "\xD0\0 \xD1\0 \xD2\0 \xD3\0 " | |
| "\xD4\0 \xD5\0 \xD6\0 \xD7\0 " | |
| "\xD8\0 \xD9\0 \xDA\0 \xDB\0 " | |
| "\xDC\0 \xDD\0 \xDE\0 \xDF\0 " | |
| "\xE0\0 \xE1\0 \xE2\0 \xE3\0 " | |
| "\xE4\0 \xE5\0 \xE6\0 \xE7\0 " | |
| "\xE8\0 \xE9\0 \xEA\0 \xEB\0 " | |
| "\xEC\0 \xED\0 \xEE\0 \xEF\0 " | |
| "\xF0\0 \xF1\0 \xF2\0 \xF3\0 " | |
| "\xF4\0 \xF5\0 \xF6\0 \xF7\0 " | |
| "\xF8\0 \xF9\0 \xFA\0 \xFB\0 " | |
| "\xFC\0 \xFD\0 \xFE\0 \xFF\0 "; | |
| JsonStringifier::JsonStringifier(Isolate* isolate) | |
| : isolate_(isolate), builder_(isolate), gap_(nullptr), indent_(0) { | |
| tojson_string_ = factory()->toJSON_string(); | |
| stack_ = factory()->NewJSArray(8); | |
| } | |
| MaybeHandle<Object> JsonStringifier::Stringify(Handle<Object> object, | |
| Handle<Object> replacer, | |
| Handle<Object> gap) { | |
| if (!InitializeReplacer(replacer)) return MaybeHandle<Object>(); | |
| if (!gap->IsUndefined(isolate_) && !InitializeGap(gap)) { | |
| return MaybeHandle<Object>(); | |
| } | |
| Result result = SerializeObject(object); | |
| if (result == UNCHANGED) return factory()->undefined_value(); | |
| if (result == SUCCESS) return builder_.Finish(); | |
| DCHECK(result == EXCEPTION); | |
| return MaybeHandle<Object>(); | |
| } | |
| bool JsonStringifier::InitializeReplacer(Handle<Object> replacer) { | |
| DCHECK(property_list_.is_null()); | |
| DCHECK(replacer_function_.is_null()); | |
| Maybe<bool> is_array = Object::IsArray(replacer); | |
| if (is_array.IsNothing()) return false; | |
| if (is_array.FromJust()) { | |
| HandleScope handle_scope(isolate_); | |
| Handle<OrderedHashSet> set = factory()->NewOrderedHashSet(); | |
| Handle<Object> length_obj; | |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( | |
| isolate_, length_obj, | |
| Object::GetLengthFromArrayLike(isolate_, | |
| Handle<JSReceiver>::cast(replacer)), | |
| false); | |
| uint32_t length; | |
| if (!length_obj->ToUint32(&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 (element->IsNumber() || element->IsString()) { | |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( | |
| isolate_, key, Object::ToString(isolate_, element), false); | |
| } else if (element->IsJSValue()) { | |
| Handle<Object> value(Handle<JSValue>::cast(element)->value(), isolate_); | |
| if (value->IsNumber() || value->IsString()) { | |
| 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); | |
| set = OrderedHashSet::Add(isolate_, set, key); | |
| } | |
| property_list_ = OrderedHashSet::ConvertToKeysArray( | |
| isolate_, set, GetKeysConversion::kKeepNumbers); | |
| property_list_ = handle_scope.CloseAndEscape(property_list_); | |
| } else if (replacer->IsCallable()) { | |
| replacer_function_ = Handle<JSReceiver>::cast(replacer); | |
| } | |
| return true; | |
| } | |
| bool JsonStringifier::InitializeGap(Handle<Object> gap) { | |
| DCHECK_NULL(gap_); | |
| HandleScope scope(isolate_); | |
| if (gap->IsJSValue()) { | |
| Handle<Object> value(Handle<JSValue>::cast(gap)->value(), isolate_); | |
| if (value->IsString()) { | |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate_, gap, | |
| Object::ToString(isolate_, gap), false); | |
| } else if (value->IsNumber()) { | |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate_, gap, | |
| Object::ToNumber(isolate_, gap), false); | |
| } | |
| } | |
| if (gap->IsString()) { | |
| Handle<String> gap_string = Handle<String>::cast(gap); | |
| if (gap_string->length() > 0) { | |
| int gap_length = std::min(gap_string->length(), 10); | |
| gap_ = NewArray<uc16>(gap_length + 1); | |
| String::WriteToFlat(*gap_string, gap_, 0, gap_length); | |
| for (int i = 0; i < gap_length; i++) { | |
| if (gap_[i] > String::kMaxOneByteCharCode) { | |
| builder_.ChangeEncoding(); | |
| break; | |
| } | |
| } | |
| gap_[gap_length] = '\0'; | |
| } | |
| } else if (gap->IsNumber()) { | |
| int num_value = DoubleToInt32(gap->Number()); | |
| if (num_value > 0) { | |
| int gap_length = std::min(num_value, 10); | |
| gap_ = NewArray<uc16>(gap_length + 1); | |
| for (int i = 0; i < gap_length; i++) gap_[i] = ' '; | |
| gap_[gap_length] = '\0'; | |
| } | |
| } | |
| return true; | |
| } | |
| MaybeHandle<Object> JsonStringifier::ApplyToJsonFunction(Handle<Object> object, | |
| Handle<Object> key) { | |
| HandleScope scope(isolate_); | |
| Handle<Object> object_for_lookup = object; | |
| if (object->IsBigInt()) { | |
| ASSIGN_RETURN_ON_EXCEPTION(isolate_, object_for_lookup, | |
| Object::ToObject(isolate_, object), Object); | |
| } | |
| DCHECK(object_for_lookup->IsJSReceiver()); | |
| // Retrieve toJSON function. | |
| Handle<Object> fun; | |
| { | |
| LookupIterator it(isolate_, object_for_lookup, tojson_string_, | |
| LookupIterator::PROTOTYPE_CHAIN_SKIP_INTERCEPTOR); | |
| ASSIGN_RETURN_ON_EXCEPTION(isolate_, fun, Object::GetProperty(&it), Object); | |
| if (!fun->IsCallable()) return object; | |
| } | |
| // Call toJSON function. | |
| if (key->IsSmi()) key = factory()->NumberToString(key); | |
| Handle<Object> argv[] = {key}; | |
| ASSIGN_RETURN_ON_EXCEPTION(isolate_, object, | |
| Execution::Call(isolate_, fun, object, 1, argv), | |
| Object); | |
| return scope.CloseAndEscape(object); | |
| } | |
| MaybeHandle<Object> JsonStringifier::ApplyReplacerFunction( | |
| Handle<Object> value, Handle<Object> key, Handle<Object> initial_holder) { | |
| HandleScope scope(isolate_); | |
| if (key->IsSmi()) key = factory()->NumberToString(key); | |
| Handle<Object> argv[] = {key, value}; | |
| Handle<JSReceiver> holder = CurrentHolder(value, initial_holder); | |
| ASSIGN_RETURN_ON_EXCEPTION( | |
| isolate_, value, | |
| Execution::Call(isolate_, replacer_function_, holder, 2, argv), Object); | |
| return scope.CloseAndEscape(value); | |
| } | |
| Handle<JSReceiver> JsonStringifier::CurrentHolder( | |
| Handle<Object> value, Handle<Object> initial_holder) { | |
| int length = Smi::ToInt(stack_->length()); | |
| if (length == 0) { | |
| Handle<JSObject> holder = | |
| factory()->NewJSObject(isolate_->object_function()); | |
| JSObject::AddProperty(isolate_, holder, factory()->empty_string(), | |
| initial_holder, NONE); | |
| return holder; | |
| } else { | |
| FixedArray* elements = FixedArray::cast(stack_->elements()); | |
| return Handle<JSReceiver>(JSReceiver::cast(elements->get(length - 1)), | |
| isolate_); | |
| } | |
| } | |
| JsonStringifier::Result JsonStringifier::StackPush(Handle<Object> object) { | |
| StackLimitCheck check(isolate_); | |
| if (check.HasOverflowed()) { | |
| isolate_->StackOverflow(); | |
| return EXCEPTION; | |
| } | |
| int length = Smi::ToInt(stack_->length()); | |
| { | |
| DisallowHeapAllocation no_allocation; | |
| FixedArray* elements = FixedArray::cast(stack_->elements()); | |
| for (int i = 0; i < length; i++) { | |
| if (elements->get(i) == *object) { | |
| AllowHeapAllocation allow_to_return_error; | |
| Handle<Object> error = | |
| factory()->NewTypeError(MessageTemplate::kCircularStructure); | |
| isolate_->Throw(*error); | |
| return EXCEPTION; | |
| } | |
| } | |
| } | |
| JSArray::SetLength(stack_, length + 1); | |
| FixedArray::cast(stack_->elements())->set(length, *object); | |
| return SUCCESS; | |
| } | |
| void JsonStringifier::StackPop() { | |
| int length = Smi::ToInt(stack_->length()); | |
| stack_->set_length(Smi::FromInt(length - 1)); | |
| } | |
| template <bool deferred_string_key> | |
| JsonStringifier::Result JsonStringifier::Serialize_(Handle<Object> object, | |
| bool comma, | |
| Handle<Object> key) { | |
| StackLimitCheck interrupt_check(isolate_); | |
| Handle<Object> initial_value = object; | |
| if (interrupt_check.InterruptRequested() && | |
| isolate_->stack_guard()->HandleInterrupts()->IsException(isolate_)) { | |
| return EXCEPTION; | |
| } | |
| if (object->IsJSReceiver() || object->IsBigInt()) { | |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( | |
| isolate_, object, ApplyToJsonFunction(object, key), EXCEPTION); | |
| } | |
| if (!replacer_function_.is_null()) { | |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( | |
| isolate_, object, ApplyReplacerFunction(object, key, initial_value), | |
| EXCEPTION); | |
| } | |
| if (object->IsSmi()) { | |
| if (deferred_string_key) SerializeDeferredKey(comma, key); | |
| return SerializeSmi(Smi::cast(*object)); | |
| } | |
| switch (HeapObject::cast(*object)->map()->instance_type()) { | |
| case HEAP_NUMBER_TYPE: | |
| case MUTABLE_HEAP_NUMBER_TYPE: | |
| if (deferred_string_key) SerializeDeferredKey(comma, key); | |
| return SerializeHeapNumber(Handle<HeapNumber>::cast(object)); | |
| case BIGINT_TYPE: | |
| isolate_->Throw( | |
| *factory()->NewTypeError(MessageTemplate::kBigIntSerializeJSON)); | |
| return EXCEPTION; | |
| case ODDBALL_TYPE: | |
| switch (Oddball::cast(*object)->kind()) { | |
| case Oddball::kFalse: | |
| if (deferred_string_key) SerializeDeferredKey(comma, key); | |
| builder_.AppendCString("false"); | |
| return SUCCESS; | |
| case Oddball::kTrue: | |
| if (deferred_string_key) SerializeDeferredKey(comma, key); | |
| builder_.AppendCString("true"); | |
| return SUCCESS; | |
| case Oddball::kNull: | |
| if (deferred_string_key) SerializeDeferredKey(comma, key); | |
| builder_.AppendCString("null"); | |
| return SUCCESS; | |
| default: | |
| return UNCHANGED; | |
| } | |
| case JS_ARRAY_TYPE: | |
| if (deferred_string_key) SerializeDeferredKey(comma, key); | |
| return SerializeJSArray(Handle<JSArray>::cast(object)); | |
| case JS_VALUE_TYPE: | |
| if (deferred_string_key) SerializeDeferredKey(comma, key); | |
| return SerializeJSValue(Handle<JSValue>::cast(object)); | |
| case SYMBOL_TYPE: | |
| return UNCHANGED; | |
| default: | |
| if (object->IsString()) { | |
| if (deferred_string_key) SerializeDeferredKey(comma, key); | |
| SerializeString(Handle<String>::cast(object)); | |
| return SUCCESS; | |
| } else { | |
| DCHECK(object->IsJSReceiver()); | |
| if (object->IsCallable()) return UNCHANGED; | |
| // Go to slow path for global proxy and objects requiring access checks. | |
| if (deferred_string_key) SerializeDeferredKey(comma, key); | |
| if (object->IsJSProxy()) { | |
| return SerializeJSProxy(Handle<JSProxy>::cast(object)); | |
| } | |
| return SerializeJSObject(Handle<JSObject>::cast(object)); | |
| } | |
| } | |
| UNREACHABLE(); | |
| } | |
| JsonStringifier::Result JsonStringifier::SerializeJSValue( | |
| Handle<JSValue> object) { | |
| Object* raw = object->value(); | |
| if (raw->IsString()) { | |
| Handle<Object> value; | |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( | |
| isolate_, value, Object::ToString(isolate_, object), EXCEPTION); | |
| SerializeString(Handle<String>::cast(value)); | |
| } else if (raw->IsNumber()) { | |
| Handle<Object> value; | |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( | |
| isolate_, value, Object::ToNumber(isolate_, object), EXCEPTION); | |
| if (value->IsSmi()) return SerializeSmi(Smi::cast(*value)); | |
| SerializeHeapNumber(Handle<HeapNumber>::cast(value)); | |
| } else if (raw->IsBigInt()) { | |
| isolate_->Throw( | |
| *factory()->NewTypeError(MessageTemplate::kBigIntSerializeJSON)); | |
| return EXCEPTION; | |
| } else if (raw->IsBoolean()) { | |
| builder_.AppendCString(raw->IsTrue(isolate_) ? "true" : "false"); | |
| } else { | |
| // ES6 24.3.2.1 step 10.c, serialize as an ordinary JSObject. | |
| return SerializeJSObject(object); | |
| } | |
| return SUCCESS; | |
| } | |
| JsonStringifier::Result JsonStringifier::SerializeSmi(Smi* object) { | |
| static const int kBufferSize = 100; | |
| char chars[kBufferSize]; | |
| Vector<char> buffer(chars, kBufferSize); | |
| builder_.AppendCString(IntToCString(object->value(), buffer)); | |
| return SUCCESS; | |
| } | |
| JsonStringifier::Result JsonStringifier::SerializeDouble(double number) { | |
| if (std::isinf(number) || std::isnan(number)) { | |
| builder_.AppendCString("null"); | |
| return SUCCESS; | |
| } | |
| static const int kBufferSize = 100; | |
| char chars[kBufferSize]; | |
| Vector<char> buffer(chars, kBufferSize); | |
| builder_.AppendCString(DoubleToCString(number, buffer)); | |
| return SUCCESS; | |
| } | |
| JsonStringifier::Result JsonStringifier::SerializeJSArray( | |
| Handle<JSArray> object) { | |
| HandleScope handle_scope(isolate_); | |
| Result stack_push = StackPush(object); | |
| if (stack_push != SUCCESS) return stack_push; | |
| uint32_t length = 0; | |
| CHECK(object->length()->ToArrayLength(&length)); | |
| DCHECK(!object->IsAccessCheckNeeded()); | |
| builder_.AppendCharacter('['); | |
| Indent(); | |
| uint32_t i = 0; | |
| if (replacer_function_.is_null()) { | |
| switch (object->GetElementsKind()) { | |
| case PACKED_SMI_ELEMENTS: { | |
| Handle<FixedArray> elements(FixedArray::cast(object->elements()), | |
| isolate_); | |
| StackLimitCheck interrupt_check(isolate_); | |
| while (i < length) { | |
| if (interrupt_check.InterruptRequested() && | |
| isolate_->stack_guard()->HandleInterrupts()->IsException( | |
| isolate_)) { | |
| return EXCEPTION; | |
| } | |
| Separator(i == 0); | |
| SerializeSmi(Smi::cast(elements->get(i))); | |
| i++; | |
| } | |
| break; | |
| } | |
| case PACKED_DOUBLE_ELEMENTS: { | |
| // Empty array is FixedArray but not FixedDoubleArray. | |
| if (length == 0) break; | |
| Handle<FixedDoubleArray> elements( | |
| FixedDoubleArray::cast(object->elements()), isolate_); | |
| StackLimitCheck interrupt_check(isolate_); | |
| while (i < length) { | |
| if (interrupt_check.InterruptRequested() && | |
| isolate_->stack_guard()->HandleInterrupts()->IsException( | |
| isolate_)) { | |
| return EXCEPTION; | |
| } | |
| Separator(i == 0); | |
| SerializeDouble(elements->get_scalar(i)); | |
| i++; | |
| } | |
| break; | |
| } | |
| case PACKED_ELEMENTS: { | |
| Handle<Object> old_length(object->length(), isolate_); | |
| while (i < length) { | |
| if (object->length() != *old_length || | |
| object->GetElementsKind() != PACKED_ELEMENTS) { | |
| // Fall back to slow path. | |
| break; | |
| } | |
| Separator(i == 0); | |
| Result result = SerializeElement( | |
| isolate_, | |
| Handle<Object>(FixedArray::cast(object->elements())->get(i), | |
| isolate_), | |
| i); | |
| if (result == UNCHANGED) { | |
| builder_.AppendCString("null"); | |
| } else if (result != SUCCESS) { | |
| return result; | |
| } | |
| i++; | |
| } | |
| break; | |
| } | |
| // The FAST_HOLEY_* cases could be handled in a faster way. They resemble | |
| // the non-holey cases except that a lookup is necessary for holes. | |
| default: | |
| break; | |
| } | |
| } | |
| if (i < length) { | |
| // Slow path for non-fast elements and fall-back in edge case. | |
| Result result = SerializeArrayLikeSlow(object, i, length); | |
| if (result != SUCCESS) return result; | |
| } | |
| Unindent(); | |
| if (length > 0) NewLine(); | |
| builder_.AppendCharacter(']'); | |
| StackPop(); | |
| return SUCCESS; | |
| } | |
| JsonStringifier::Result JsonStringifier::SerializeArrayLikeSlow( | |
| Handle<JSReceiver> object, uint32_t start, uint32_t length) { | |
| // We need to write out at least two characters per array element. | |
| static const int kMaxSerializableArrayLength = String::kMaxLength / 2; | |
| if (length > kMaxSerializableArrayLength) { | |
| isolate_->Throw(*isolate_->factory()->NewInvalidStringLengthError()); | |
| return EXCEPTION; | |
| } | |
| for (uint32_t i = start; i < length; i++) { | |
| Separator(i == 0); | |
| Handle<Object> element; | |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( | |
| isolate_, element, JSReceiver::GetElement(isolate_, object, i), | |
| EXCEPTION); | |
| Result result = SerializeElement(isolate_, element, i); | |
| if (result == SUCCESS) continue; | |
| if (result == UNCHANGED) { | |
| // Detect overflow sooner for large sparse arrays. | |
| if (builder_.HasOverflowed()) return EXCEPTION; | |
| builder_.AppendCString("null"); | |
| } else { | |
| return result; | |
| } | |
| } | |
| return SUCCESS; | |
| } | |
| JsonStringifier::Result JsonStringifier::SerializeJSObject( | |
| Handle<JSObject> object) { | |
| HandleScope handle_scope(isolate_); | |
| Result stack_push = StackPush(object); | |
| if (stack_push != SUCCESS) return stack_push; | |
| if (property_list_.is_null() && | |
| !object->map()->IsCustomElementsReceiverMap() && | |
| object->HasFastProperties() && | |
| Handle<JSObject>::cast(object)->elements()->length() == 0) { | |
| DCHECK(object->IsJSObject()); | |
| DCHECK(!object->IsJSGlobalProxy()); | |
| Handle<JSObject> js_obj = Handle<JSObject>::cast(object); | |
| DCHECK(!js_obj->HasIndexedInterceptor()); | |
| DCHECK(!js_obj->HasNamedInterceptor()); | |
| Handle<Map> map(js_obj->map(), isolate_); | |
| builder_.AppendCharacter('{'); | |
| Indent(); | |
| bool comma = false; | |
| for (int i = 0; i < map->NumberOfOwnDescriptors(); i++) { | |
| Handle<Name> name(map->instance_descriptors()->GetKey(i), isolate_); | |
| // TODO(rossberg): Should this throw? | |
| if (!name->IsString()) continue; | |
| Handle<String> key = Handle<String>::cast(name); | |
| PropertyDetails details = map->instance_descriptors()->GetDetails(i); | |
| if (details.IsDontEnum()) continue; | |
| Handle<Object> property; | |
| if (details.location() == kField && *map == js_obj->map()) { | |
| DCHECK_EQ(kData, details.kind()); | |
| FieldIndex field_index = FieldIndex::ForDescriptor(*map, i); | |
| property = JSObject::FastPropertyAt(js_obj, details.representation(), | |
| field_index); | |
| } else { | |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( | |
| isolate_, property, | |
| Object::GetPropertyOrElement(isolate_, js_obj, key), EXCEPTION); | |
| } | |
| Result result = SerializeProperty(property, comma, key); | |
| if (!comma && result == SUCCESS) comma = true; | |
| if (result == EXCEPTION) return result; | |
| } | |
| Unindent(); | |
| if (comma) NewLine(); | |
| builder_.AppendCharacter('}'); | |
| } else { | |
| Result result = SerializeJSReceiverSlow(object); | |
| if (result != SUCCESS) return result; | |
| } | |
| StackPop(); | |
| return SUCCESS; | |
| } | |
| JsonStringifier::Result JsonStringifier::SerializeJSReceiverSlow( | |
| Handle<JSReceiver> object) { | |
| Handle<FixedArray> contents = property_list_; | |
| if (contents.is_null()) { | |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( | |
| isolate_, contents, | |
| KeyAccumulator::GetKeys(object, KeyCollectionMode::kOwnOnly, | |
| ENUMERABLE_STRINGS, | |
| GetKeysConversion::kConvertToString), | |
| EXCEPTION); | |
| } | |
| builder_.AppendCharacter('{'); | |
| Indent(); | |
| bool comma = false; | |
| for (int i = 0; i < contents->length(); i++) { | |
| Handle<String> key(String::cast(contents->get(i)), isolate_); | |
| Handle<Object> property; | |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( | |
| isolate_, property, Object::GetPropertyOrElement(isolate_, object, key), | |
| EXCEPTION); | |
| Result result = SerializeProperty(property, comma, key); | |
| if (!comma && result == SUCCESS) comma = true; | |
| if (result == EXCEPTION) return result; | |
| } | |
| Unindent(); | |
| if (comma) NewLine(); | |
| builder_.AppendCharacter('}'); | |
| return SUCCESS; | |
| } | |
| JsonStringifier::Result JsonStringifier::SerializeJSProxy( | |
| Handle<JSProxy> object) { | |
| HandleScope scope(isolate_); | |
| Result stack_push = StackPush(object); | |
| if (stack_push != SUCCESS) return stack_push; | |
| Maybe<bool> is_array = Object::IsArray(object); | |
| if (is_array.IsNothing()) return EXCEPTION; | |
| if (is_array.FromJust()) { | |
| Handle<Object> length_object; | |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( | |
| isolate_, length_object, | |
| Object::GetLengthFromArrayLike(isolate_, | |
| Handle<JSReceiver>::cast(object)), | |
| EXCEPTION); | |
| uint32_t length; | |
| if (!length_object->ToUint32(&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; | |
| } | |
| builder_.AppendCharacter('['); | |
| Indent(); | |
| Result result = SerializeArrayLikeSlow(object, 0, length); | |
| if (result != SUCCESS) return result; | |
| Unindent(); | |
| if (length > 0) NewLine(); | |
| builder_.AppendCharacter(']'); | |
| } else { | |
| Result result = SerializeJSReceiverSlow(object); | |
| if (result != SUCCESS) return result; | |
| } | |
| StackPop(); | |
| return SUCCESS; | |
| } | |
| template <typename SrcChar, typename DestChar> | |
| void JsonStringifier::SerializeStringUnchecked_( | |
| Vector<const SrcChar> src, | |
| IncrementalStringBuilder::NoExtend<DestChar>* dest) { | |
| // Assert that uc16 character is not truncated down to 8 bit. | |
| // The <uc16, char> version of this method must not be called. | |
| DCHECK(sizeof(DestChar) >= sizeof(SrcChar)); | |
| for (int i = 0; i < src.length(); i++) { | |
| SrcChar c = src[i]; | |
| if (DoNotEscape(c)) { | |
| dest->Append(c); | |
| } else { | |
| dest->AppendCString(&JsonEscapeTable[c * kJsonEscapeTableEntrySize]); | |
| } | |
| } | |
| } | |
| template <typename SrcChar, typename DestChar> | |
| void JsonStringifier::SerializeString_(Handle<String> string) { | |
| int length = string->length(); | |
| builder_.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. | |
| if (int worst_case_length = builder_.EscapedLengthIfCurrentPartFits(length)) { | |
| DisallowHeapAllocation no_gc; | |
| Vector<const SrcChar> vector = string->GetCharVector<SrcChar>(); | |
| IncrementalStringBuilder::NoExtendBuilder<DestChar> no_extend( | |
| &builder_, worst_case_length); | |
| SerializeStringUnchecked_(vector, &no_extend); | |
| } else { | |
| FlatStringReader reader(isolate_, string); | |
| for (int i = 0; i < reader.length(); i++) { | |
| SrcChar c = reader.Get<SrcChar>(i); | |
| if (DoNotEscape(c)) { | |
| builder_.Append<SrcChar, DestChar>(c); | |
| } else { | |
| builder_.AppendCString(&JsonEscapeTable[c * kJsonEscapeTableEntrySize]); | |
| } | |
| } | |
| } | |
| builder_.Append<uint8_t, DestChar>('"'); | |
| } | |
| template <> | |
| bool JsonStringifier::DoNotEscape(uint8_t c) { | |
| return c >= '#' && c <= '~' && c != '\\'; | |
| } | |
| template <> | |
| bool JsonStringifier::DoNotEscape(uint16_t c) { | |
| return c >= '#' && c != '\\' && c != 0x7F; | |
| } | |
| void JsonStringifier::NewLine() { | |
| if (gap_ == nullptr) return; | |
| builder_.AppendCharacter('\n'); | |
| for (int i = 0; i < indent_; i++) builder_.AppendCString(gap_); | |
| } | |
| void JsonStringifier::Separator(bool first) { | |
| if (!first) builder_.AppendCharacter(','); | |
| NewLine(); | |
| } | |
| void JsonStringifier::SerializeDeferredKey(bool deferred_comma, | |
| Handle<Object> deferred_key) { | |
| Separator(!deferred_comma); | |
| SerializeString(Handle<String>::cast(deferred_key)); | |
| builder_.AppendCharacter(':'); | |
| if (gap_ != nullptr) builder_.AppendCharacter(' '); | |
| } | |
| void JsonStringifier::SerializeString(Handle<String> object) { | |
| object = String::Flatten(isolate_, object); | |
| if (builder_.CurrentEncoding() == String::ONE_BYTE_ENCODING) { | |
| if (object->IsOneByteRepresentationUnderneath()) { | |
| SerializeString_<uint8_t, uint8_t>(object); | |
| } else { | |
| builder_.ChangeEncoding(); | |
| SerializeString(object); | |
| } | |
| } else { | |
| if (object->IsOneByteRepresentationUnderneath()) { | |
| SerializeString_<uint8_t, uc16>(object); | |
| } else { | |
| SerializeString_<uc16, uc16>(object); | |
| } | |
| } | |
| } | |
| } // namespace internal | |
| } // namespace v8 |