// 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 \177\0 " "\200\0 \201\0 \202\0 \203\0 " "\204\0 \205\0 \206\0 \207\0 " "\210\0 \211\0 \212\0 \213\0 " "\214\0 \215\0 \216\0 \217\0 " "\220\0 \221\0 \222\0 \223\0 " "\224\0 \225\0 \226\0 \227\0 " "\230\0 \231\0 \232\0 \233\0 " "\234\0 \235\0 \236\0 \237\0 " "\240\0 \241\0 \242\0 \243\0 " "\244\0 \245\0 \246\0 \247\0 " "\250\0 \251\0 \252\0 \253\0 " "\254\0 \255\0 \256\0 \257\0 " "\260\0 \261\0 \262\0 \263\0 " "\264\0 \265\0 \266\0 \267\0 " "\270\0 \271\0 \272\0 \273\0 " "\274\0 \275\0 \276\0 \277\0 " "\300\0 \301\0 \302\0 \303\0 " "\304\0 \305\0 \306\0 \307\0 " "\310\0 \311\0 \312\0 \313\0 " "\314\0 \315\0 \316\0 \317\0 " "\320\0 \321\0 \322\0 \323\0 " "\324\0 \325\0 \326\0 \327\0 " "\330\0 \331\0 \332\0 \333\0 " "\334\0 \335\0 \336\0 \337\0 " "\340\0 \341\0 \342\0 \343\0 " "\344\0 \345\0 \346\0 \347\0 " "\350\0 \351\0 \352\0 \353\0 " "\354\0 \355\0 \356\0 \357\0 " "\360\0 \361\0 \362\0 \363\0 " "\364\0 \365\0 \366\0 \367\0 " "\370\0 \371\0 \372\0 \373\0 " "\374\0 \375\0 \376\0 \377\0 "; JsonStringifier::JsonStringifier(Isolate* isolate) : isolate_(isolate), builder_(isolate), gap_(nullptr), indent_(0) { tojson_string_ = factory()->toJSON_string(); stack_ = factory()->NewJSArray(8); } MaybeHandle JsonStringifier::Stringify(Handle object, Handle replacer, Handle gap) { if (!InitializeReplacer(replacer)) return MaybeHandle(); if (!gap->IsUndefined(isolate_) && !InitializeGap(gap)) { return MaybeHandle(); } Result result = SerializeObject(object); if (result == UNCHANGED) return factory()->undefined_value(); if (result == SUCCESS) return builder_.Finish(); DCHECK(result == EXCEPTION); return MaybeHandle(); } bool IsInList(Handle key, List >* list) { // TODO(yangguo): This is O(n^2) for n properties in the list. Deal with this // if this becomes an issue. for (const Handle& existing : *list) { if (String::Equals(existing, key)) return true; } return false; } bool JsonStringifier::InitializeReplacer(Handle replacer) { DCHECK(property_list_.is_null()); DCHECK(replacer_function_.is_null()); Maybe is_array = Object::IsArray(replacer); if (is_array.IsNothing()) return false; if (is_array.FromJust()) { HandleScope handle_scope(isolate_); List > list; Handle 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 element; Handle 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 value(Handle::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; if (!IsInList(key, &list)) list.Add(key); } property_list_ = factory()->NewUninitializedFixedArray(list.length()); for (int i = 0; i < list.length(); i++) { property_list_->set(i, *list[i]); } property_list_ = handle_scope.CloseAndEscape(property_list_); } else if (replacer->IsCallable()) { replacer_function_ = Handle::cast(replacer); } return true; } bool JsonStringifier::InitializeGap(Handle gap) { DCHECK_NULL(gap_); HandleScope scope(isolate_); if (gap->IsJSValue()) { Handle value(Handle::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 gap_string = Handle::cast(gap); if (gap_string->length() > 0) { int gap_length = std::min(gap_string->length(), 10); gap_ = NewArray(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(gap_length + 1); for (int i = 0; i < gap_length; i++) gap_[i] = ' '; gap_[gap_length] = '\0'; } } return true; } MaybeHandle JsonStringifier::ApplyToJsonFunction(Handle object, Handle key) { HandleScope scope(isolate_); LookupIterator it(object, tojson_string_, LookupIterator::PROTOTYPE_CHAIN_SKIP_INTERCEPTOR); Handle fun; 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 argv[] = {key}; ASSIGN_RETURN_ON_EXCEPTION(isolate_, object, Execution::Call(isolate_, fun, object, 1, argv), Object); return scope.CloseAndEscape(object); } MaybeHandle JsonStringifier::ApplyReplacerFunction( Handle value, Handle key, Handle initial_holder) { HandleScope scope(isolate_); if (key->IsSmi()) key = factory()->NumberToString(key); Handle argv[] = {key, value}; Handle 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 JsonStringifier::CurrentHolder( Handle value, Handle initial_holder) { int length = Smi::cast(stack_->length())->value(); if (length == 0) { Handle 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::cast(elements->get(length - 1)), isolate_); } } JsonStringifier::Result JsonStringifier::StackPush(Handle object) { StackLimitCheck check(isolate_); if (check.HasOverflowed()) { isolate_->StackOverflow(); return EXCEPTION; } int length = Smi::cast(stack_->length())->value(); { 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 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::cast(stack_->length())->value(); stack_->set_length(Smi::FromInt(length - 1)); } template JsonStringifier::Result JsonStringifier::Serialize_(Handle object, bool comma, Handle key) { StackLimitCheck interrupt_check(isolate_); Handle initial_value = object; if (interrupt_check.InterruptRequested() && isolate_->stack_guard()->HandleInterrupts()->IsException(isolate_)) { return EXCEPTION; } if (object->IsJSReceiver()) { 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::cast(object)); 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::cast(object)); case JS_VALUE_TYPE: if (deferred_string_key) SerializeDeferredKey(comma, key); return SerializeJSValue(Handle::cast(object)); case SYMBOL_TYPE: return UNCHANGED; default: if (object->IsString()) { if (deferred_string_key) SerializeDeferredKey(comma, key); SerializeString(Handle::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::cast(object)); } return SerializeJSObject(Handle::cast(object)); } } UNREACHABLE(); return UNCHANGED; } JsonStringifier::Result JsonStringifier::SerializeJSValue( Handle object) { String* class_name = object->class_name(); if (class_name == isolate_->heap()->String_string()) { Handle value; ASSIGN_RETURN_ON_EXCEPTION_VALUE( isolate_, value, Object::ToString(isolate_, object), EXCEPTION); SerializeString(Handle::cast(value)); } else if (class_name == isolate_->heap()->Number_string()) { Handle value; ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate_, value, Object::ToNumber(object), EXCEPTION); if (value->IsSmi()) return SerializeSmi(Smi::cast(*value)); SerializeHeapNumber(Handle::cast(value)); } else if (class_name == isolate_->heap()->Boolean_string()) { Object* value = JSValue::cast(*object)->value(); DCHECK(value->IsBoolean()); builder_.AppendCString(value->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 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 buffer(chars, kBufferSize); builder_.AppendCString(DoubleToCString(number, buffer)); return SUCCESS; } JsonStringifier::Result JsonStringifier::SerializeJSArray( Handle 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 FAST_SMI_ELEMENTS: { Handle 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 FAST_DOUBLE_ELEMENTS: { // Empty array is FixedArray but not FixedDoubleArray. if (length == 0) break; Handle 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 FAST_ELEMENTS: { Handle old_length(object->length(), isolate_); while (i < length) { if (object->length() != *old_length || object->GetElementsKind() != FAST_ELEMENTS) { // Fall back to slow path. break; } Separator(i == 0); Result result = SerializeElement( isolate_, Handle(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 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 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 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::cast(object)->elements()->length() == 0) { DCHECK(object->IsJSObject()); DCHECK(!object->IsJSGlobalProxy()); Handle js_obj = Handle::cast(object); DCHECK(!js_obj->HasIndexedInterceptor()); DCHECK(!js_obj->HasNamedInterceptor()); Handle map(js_obj->map()); builder_.AppendCharacter('{'); Indent(); bool comma = false; for (int i = 0; i < map->NumberOfOwnDescriptors(); i++) { Handle name(map->instance_descriptors()->GetKey(i), isolate_); // TODO(rossberg): Should this throw? if (!name->IsString()) continue; Handle key = Handle::cast(name); PropertyDetails details = map->instance_descriptors()->GetDetails(i); if (details.IsDontEnum()) continue; Handle 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 object) { Handle 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 key(String::cast(contents->get(i)), isolate_); Handle 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 object) { HandleScope scope(isolate_); Result stack_push = StackPush(object); if (stack_push != SUCCESS) return stack_push; Maybe is_array = Object::IsArray(object); if (is_array.IsNothing()) return EXCEPTION; if (is_array.FromJust()) { Handle 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 void JsonStringifier::SerializeStringUnchecked_( Vector src, IncrementalStringBuilder::NoExtend* dest) { // Assert that uc16 character is not truncated down to 8 bit. // The 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 void JsonStringifier::SerializeString_(Handle string) { int length = string->length(); builder_.Append('"'); // We make a rough estimate to find out if the current string can be // serialized without allocating a new string part. The worst case length of // an escaped character is 6. Shifting the remainin string length right by 3 // is a more pessimistic estimate, but faster to calculate. int worst_case_length = length << 3; if (builder_.CurrentPartCanFit(worst_case_length)) { DisallowHeapAllocation no_gc; Vector vector = string->GetCharVector(); IncrementalStringBuilder::NoExtendBuilder 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(i); if (DoNotEscape(c)) { builder_.Append(c); } else { builder_.AppendCString(&JsonEscapeTable[c * kJsonEscapeTableEntrySize]); } } } builder_.Append('"'); } 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 deferred_key) { Separator(!deferred_comma); SerializeString(Handle::cast(deferred_key)); builder_.AppendCharacter(':'); if (gap_ != nullptr) builder_.AppendCharacter(' '); } void JsonStringifier::SerializeString(Handle object) { object = String::Flatten(object); if (builder_.CurrentEncoding() == String::ONE_BYTE_ENCODING) { if (object->IsOneByteRepresentationUnderneath()) { SerializeString_(object); } else { builder_.ChangeEncoding(); SerializeString(object); } } else { if (object->IsOneByteRepresentationUnderneath()) { SerializeString_(object); } else { SerializeString_(object); } } } } // namespace internal } // namespace v8