| // 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/lookup.h" |
| #include "src/messages.h" |
| #include "src/objects-inl.h" |
| #include "src/utils.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| // 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>(); |
| } |
| PostponeInterruptsScope no_debug_breaks(isolate_, StackGuard::DEBUGBREAK); |
| 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_, 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(set, key); |
| } |
| property_list_ = OrderedHashSet::ConvertToKeysArray( |
| 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(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(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(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(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()->instance_type() > LAST_CUSTOM_ELEMENTS_RECEIVER && |
| 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()); |
| 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(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(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_, 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(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 |