From a19404f04a3cae302ad0ad2e172d064695a6b71f Mon Sep 17 00:00:00 2001 From: yangguo Date: Fri, 20 May 2016 06:19:02 -0700 Subject: [PATCH] [json] handle proxies in BasicJsonSerializer. R=cbruni@chromium.org Review-Url: https://codereview.chromium.org/1994183002 Cr-Commit-Position: refs/heads/master@{#36409} --- src/builtins.cc | 6 +- src/js/json.js | 6 +- src/json-stringifier.h | 155 ++++++++++++++++++++----------- src/objects.cc | 17 ++-- src/objects.h | 4 + test/mjsunit/es6/proxies-json.js | 53 +++++++++++ 6 files changed, 173 insertions(+), 68 deletions(-) diff --git a/src/builtins.cc b/src/builtins.cc index d869fd0961..30ec45199a 100644 --- a/src/builtins.cc +++ b/src/builtins.cc @@ -1088,12 +1088,8 @@ bool IterateElements(Isolate* isolate, Handle receiver, length = static_cast(array->length()->Number()); } else { Handle val; - Handle key = isolate->factory()->length_string(); ASSIGN_RETURN_ON_EXCEPTION_VALUE( - isolate, val, Runtime::GetObjectProperty(isolate, receiver, key), - false); - ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, val, - Object::ToLength(isolate, val), false); + isolate, val, Object::GetLengthFromArrayLike(isolate, receiver), false); // TODO(caitp): Support larger element indexes (up to 2^53-1). if (!val->ToUint32(&length)) { length = 0; diff --git a/src/js/json.js b/src/js/json.js index 09fe688ebb..f5c0ae7a3c 100644 --- a/src/js/json.js +++ b/src/js/json.js @@ -201,9 +201,7 @@ function JSONSerialize(key, holder, replacer, stack, indent, gap) { function JSONStringify(value, replacer, space) { - if (arguments.length === 1 && !IS_PROXY(value)) { - return %BasicJSONStringify(value, ""); - } + if (arguments.length === 1) return %BasicJSONStringify(value, ""); if (!IS_CALLABLE(replacer) && %is_arraylike(replacer)) { var property_list = new InternalArray(); var seen_properties = new GlobalSet(); @@ -248,7 +246,7 @@ function JSONStringify(value, replacer, space) { } else { gap = ""; } - if (!IS_CALLABLE(replacer) && !property_list && !IS_PROXY(value)) { + if (!IS_CALLABLE(replacer) && !property_list) { return %BasicJSONStringify(value, gap); } return JSONSerialize('', {'': value}, replacer, new Stack(), "", gap); diff --git a/src/json-stringifier.h b/src/json-stringifier.h index 0b084e070d..8172b7080c 100644 --- a/src/json-stringifier.h +++ b/src/json-stringifier.h @@ -85,8 +85,10 @@ class BasicJsonStringifier BASE_EMBEDDED { INLINE(Result SerializeJSArray(Handle object)); INLINE(Result SerializeJSObject(Handle object)); - Result SerializeJSArraySlow(Handle object, uint32_t start, - uint32_t length); + Result SerializeJSProxy(Handle object); + Result SerializeJSReceiverSlow(Handle object); + Result SerializeArrayLikeSlow(Handle object, uint32_t start, + uint32_t length); void SerializeString(Handle object); @@ -326,7 +328,7 @@ void BasicJsonStringifier::StackPop() { template BasicJsonStringifier::Result BasicJsonStringifier::Serialize_( Handle object, bool comma, Handle key) { - if (object->IsJSObject()) { + if (object->IsJSReceiver()) { ASSIGN_RETURN_ON_EXCEPTION_VALUE( isolate_, object, ApplyToJsonFunction(object, key), @@ -372,11 +374,14 @@ BasicJsonStringifier::Result BasicJsonStringifier::Serialize_( if (deferred_string_key) SerializeDeferredKey(comma, key); SerializeString(Handle::cast(object)); return SUCCESS; - } else if (object->IsJSObject()) { + } else if (object->IsJSReceiver()) { if (object->IsCallable()) return UNCHANGED; // Go to slow path for global proxy and objects requiring access checks. if (object->IsAccessCheckNeeded() || object->IsJSGlobalProxy()) break; if (deferred_string_key) SerializeDeferredKey(comma, key); + if (object->IsJSProxy()) { + return SerializeJSProxy(Handle::cast(object)); + } return SerializeJSObject(Handle::cast(object)); } } @@ -494,7 +499,7 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSArray( for (uint32_t i = 0; i < length; i++) { if (object->length() != *old_length || object->GetElementsKind() != FAST_ELEMENTS) { - Result result = SerializeJSArraySlow(object, i, length); + Result result = SerializeArrayLikeSlow(object, i, length); if (result != SUCCESS) return result; break; } @@ -516,7 +521,7 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSArray( // 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: { - Result result = SerializeJSArraySlow(object, 0, length); + Result result = SerializeArrayLikeSlow(object, 0, length); if (result != SUCCESS) return result; break; } @@ -528,9 +533,8 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSArray( return SUCCESS; } - -BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSArraySlow( - Handle object, uint32_t start, uint32_t length) { +BasicJsonStringifier::Result BasicJsonStringifier::SerializeArrayLikeSlow( + Handle object, uint32_t start, uint32_t length) { for (uint32_t i = start; i < length; i++) { Separator(i == 0); Handle element; @@ -552,7 +556,6 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSArraySlow( return SUCCESS; } - BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSObject( Handle object) { HandleScope handle_scope(isolate_); @@ -560,15 +563,17 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSObject( if (stack_push != SUCCESS) return stack_push; DCHECK(!object->IsJSGlobalProxy() && !object->IsJSGlobalObject()); - builder_.AppendCharacter('{'); - Indent(); - bool comma = false; - - if (object->HasFastProperties() && - !object->HasIndexedInterceptor() && - !object->HasNamedInterceptor() && - object->elements()->length() == 0) { - Handle map(object->map()); + if (object->map()->instance_type() > LAST_CUSTOM_ELEMENTS_RECEIVER && + object->HasFastProperties() && + Handle::cast(object)->elements()->length() == 0) { + DCHECK(object->IsJSObject()); + 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? @@ -577,60 +582,104 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSObject( PropertyDetails details = map->instance_descriptors()->GetDetails(i); if (details.IsDontEnum()) continue; Handle property; - if (details.type() == DATA && *map == object->map()) { + if (details.type() == DATA && *map == js_obj->map()) { FieldIndex field_index = FieldIndex::ForDescriptor(*map, i); - Isolate* isolate = object->GetIsolate(); - if (object->IsUnboxedDoubleField(field_index)) { - double value = object->RawFastDoublePropertyAt(field_index); - property = isolate->factory()->NewHeapNumber(value); - + if (js_obj->IsUnboxedDoubleField(field_index)) { + double value = js_obj->RawFastDoublePropertyAt(field_index); + property = isolate_->factory()->NewHeapNumber(value); } else { - property = handle(object->RawFastPropertyAt(field_index), isolate); + property = handle(js_obj->RawFastPropertyAt(field_index), isolate_); } } else { ASSIGN_RETURN_ON_EXCEPTION_VALUE( - isolate_, property, - Object::GetPropertyOrElement(object, key), + 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 { - Handle contents; - ASSIGN_RETURN_ON_EXCEPTION_VALUE( - isolate_, contents, - JSReceiver::GetKeys(object, OWN_ONLY, ENUMERABLE_STRINGS), EXCEPTION); + Result result = SerializeJSReceiverSlow(object); + if (result != SUCCESS) return result; + } + StackPop(); + return SUCCESS; +} - for (int i = 0; i < contents->length(); i++) { - Object* key = contents->get(i); - Handle key_handle; - MaybeHandle maybe_property; - if (key->IsString()) { - key_handle = Handle(String::cast(key), isolate_); - maybe_property = Object::GetPropertyOrElement(object, key_handle); +BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSReceiverSlow( + Handle object) { + Handle contents; + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate_, contents, + JSReceiver::GetKeys(object, OWN_ONLY, ENUMERABLE_STRINGS), EXCEPTION); + + builder_.AppendCharacter('{'); + Indent(); + bool comma = false; + for (int i = 0; i < contents->length(); i++) { + Object* key = contents->get(i); + Handle key_handle; + MaybeHandle maybe_property; + if (key->IsString()) { + key_handle = Handle(String::cast(key), isolate_); + maybe_property = Object::GetPropertyOrElement(object, key_handle); + } else { + DCHECK(key->IsNumber()); + key_handle = factory()->NumberToString(Handle(key, isolate_)); + if (key->IsSmi()) { + maybe_property = + JSReceiver::GetElement(isolate_, object, Smi::cast(key)->value()); } else { - DCHECK(key->IsNumber()); - key_handle = factory()->NumberToString(Handle(key, isolate_)); - if (key->IsSmi()) { - maybe_property = - JSReceiver::GetElement(isolate_, object, Smi::cast(key)->value()); - } else { - maybe_property = Object::GetPropertyOrElement(object, key_handle); - } + maybe_property = Object::GetPropertyOrElement(object, key_handle); } - Handle property; - ASSIGN_RETURN_ON_EXCEPTION_VALUE( - isolate_, property, maybe_property, EXCEPTION); - Result result = SerializeProperty(property, comma, key_handle); - if (!comma && result == SUCCESS) comma = true; - if (result == EXCEPTION) return result; } + Handle property; + ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate_, property, maybe_property, + EXCEPTION); + Result result = SerializeProperty(property, comma, key_handle); + if (!comma && result == SUCCESS) comma = true; + if (result == EXCEPTION) return result; } Unindent(); if (comma) NewLine(); builder_.AppendCharacter('}'); + return SUCCESS; +} + +BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSProxy( + Handle object) { + 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; } diff --git a/src/objects.cc b/src/objects.cc index 3c23e011fc..3a33996b1b 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -719,14 +719,9 @@ MaybeHandle Object::CreateListFromArrayLike( } // 4. Let len be ? ToLength(? Get(obj, "length")). Handle receiver = Handle::cast(object); - Handle raw_length_obj; - ASSIGN_RETURN_ON_EXCEPTION( - isolate, raw_length_obj, - JSReceiver::GetProperty(receiver, isolate->factory()->length_string()), - FixedArray); Handle raw_length_number; ASSIGN_RETURN_ON_EXCEPTION(isolate, raw_length_number, - Object::ToLength(isolate, raw_length_obj), + Object::GetLengthFromArrayLike(isolate, receiver), FixedArray); uint32_t len; if (!raw_length_number->ToUint32(&len) || @@ -772,6 +767,16 @@ MaybeHandle Object::CreateListFromArrayLike( } +// static +MaybeHandle Object::GetLengthFromArrayLike(Isolate* isolate, + Handle object) { + Handle val; + Handle key = isolate->factory()->length_string(); + ASSIGN_RETURN_ON_EXCEPTION( + isolate, val, Runtime::GetObjectProperty(isolate, object, key), Object); + return Object::ToLength(isolate, val); +} + // static Maybe JSReceiver::HasProperty(LookupIterator* it) { for (; it->IsFound(); it->Next()) { diff --git a/src/objects.h b/src/objects.h index acc4b84c60..8cc551f508 100644 --- a/src/objects.h +++ b/src/objects.h @@ -1177,6 +1177,10 @@ class Object { MUST_USE_RESULT static MaybeHandle CreateListFromArrayLike( Isolate* isolate, Handle object, ElementTypes element_types); + // Get length property and apply ToLength. + MUST_USE_RESULT static MaybeHandle GetLengthFromArrayLike( + Isolate* isolate, Handle object); + // Check whether |object| is an instance of Error or NativeError. static bool IsErrorObject(Isolate* isolate, Handle object); diff --git a/test/mjsunit/es6/proxies-json.js b/test/mjsunit/es6/proxies-json.js index 03dfabecea..6b40e3ee7d 100644 --- a/test/mjsunit/es6/proxies-json.js +++ b/test/mjsunit/es6/proxies-json.js @@ -507,3 +507,56 @@ for (var i in log) assertSame(target, log[i][1]); assertEquals(["get", target, "length", proxy], log[0]); assertEquals(["get", target, "0", proxy], log[1]); assertEquals(["deleteProperty", target, "0"], log[2]); + +proxy = new Proxy([], { + get: function(target, property) { + if (property == "length") return 7; + return 0; + }, +}); +assertEquals('[[0,0,0,0,0,0,0]]', JSON.stringify([proxy])); + +proxy = new Proxy([], { + get: function(target, property) { + if (property == "length") return 1E40; + return 0; + }, +}); +assertThrows(() => JSON.stringify([proxy]), RangeError); + +log = []; +proxy = new Proxy({}, { + ownKeys: function() { + log.push("ownKeys"); + return ["0", "a", "b"]; + }, + get: function(target, property) { + log.push("get " + property); + return property.toUpperCase(); + }, + getOwnPropertyDescriptor: function(target, property) { + log.push("descriptor " + property); + return {enumerable: true, configurable: true}; + }, + isExtensible: assertUnreachable, + has: assertUnreachable, + getPrototypeOf: assertUnreachable, + setPrototypeOf: assertUnreachable, + preventExtensions: assertUnreachable, + setPrototypeOf: assertUnreachable, + defineProperty: assertUnreachable, + set: assertUnreachable, + deleteProperty: assertUnreachable, + apply: assertUnreachable, + construct: assertUnreachable, +}); + +assertEquals('[{"0":"0","a":"A","b":"B"}]', JSON.stringify([proxy])); +assertEquals(['get toJSON', + 'ownKeys', + 'descriptor 0', + 'descriptor a', + 'descriptor b', + 'get 0', + 'get a', + 'get b'], log);