[json] handle proxies in BasicJsonSerializer.

R=cbruni@chromium.org

Review-Url: https://codereview.chromium.org/1994183002
Cr-Commit-Position: refs/heads/master@{#36409}
This commit is contained in:
yangguo 2016-05-20 06:19:02 -07:00 committed by Commit bot
parent b71f1cc2f4
commit a19404f04a
6 changed files with 173 additions and 68 deletions

View File

@ -1088,12 +1088,8 @@ bool IterateElements(Isolate* isolate, Handle<JSReceiver> receiver,
length = static_cast<uint32_t>(array->length()->Number());
} else {
Handle<Object> val;
Handle<Object> 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;

View File

@ -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);

View File

@ -85,8 +85,10 @@ class BasicJsonStringifier BASE_EMBEDDED {
INLINE(Result SerializeJSArray(Handle<JSArray> object));
INLINE(Result SerializeJSObject(Handle<JSObject> object));
Result SerializeJSArraySlow(Handle<JSArray> object, uint32_t start,
uint32_t length);
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);
@ -326,7 +328,7 @@ void BasicJsonStringifier::StackPop() {
template <bool deferred_string_key>
BasicJsonStringifier::Result BasicJsonStringifier::Serialize_(
Handle<Object> object, bool comma, Handle<Object> 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<String>::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<JSProxy>::cast(object));
}
return SerializeJSObject(Handle<JSObject>::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<JSArray> object, uint32_t start, uint32_t length) {
BasicJsonStringifier::Result BasicJsonStringifier::SerializeArrayLikeSlow(
Handle<JSReceiver> object, uint32_t start, uint32_t length) {
for (uint32_t i = start; i < length; i++) {
Separator(i == 0);
Handle<Object> element;
@ -552,7 +556,6 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSArraySlow(
return SUCCESS;
}
BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSObject(
Handle<JSObject> 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> map(object->map());
if (object->map()->instance_type() > LAST_CUSTOM_ELEMENTS_RECEIVER &&
object->HasFastProperties() &&
Handle<JSObject>::cast(object)->elements()->length() == 0) {
DCHECK(object->IsJSObject());
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?
@ -577,60 +582,104 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSObject(
PropertyDetails details = map->instance_descriptors()->GetDetails(i);
if (details.IsDontEnum()) continue;
Handle<Object> 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<FixedArray> 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<String> key_handle;
MaybeHandle<Object> maybe_property;
if (key->IsString()) {
key_handle = Handle<String>(String::cast(key), isolate_);
maybe_property = Object::GetPropertyOrElement(object, key_handle);
BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSReceiverSlow(
Handle<JSReceiver> object) {
Handle<FixedArray> 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<String> key_handle;
MaybeHandle<Object> maybe_property;
if (key->IsString()) {
key_handle = Handle<String>(String::cast(key), isolate_);
maybe_property = Object::GetPropertyOrElement(object, key_handle);
} else {
DCHECK(key->IsNumber());
key_handle = factory()->NumberToString(Handle<Object>(key, isolate_));
if (key->IsSmi()) {
maybe_property =
JSReceiver::GetElement(isolate_, object, Smi::cast(key)->value());
} else {
DCHECK(key->IsNumber());
key_handle = factory()->NumberToString(Handle<Object>(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<Object> 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<Object> 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<JSProxy> object) {
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;
}

View File

@ -719,14 +719,9 @@ MaybeHandle<FixedArray> Object::CreateListFromArrayLike(
}
// 4. Let len be ? ToLength(? Get(obj, "length")).
Handle<JSReceiver> receiver = Handle<JSReceiver>::cast(object);
Handle<Object> raw_length_obj;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, raw_length_obj,
JSReceiver::GetProperty(receiver, isolate->factory()->length_string()),
FixedArray);
Handle<Object> 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<FixedArray> Object::CreateListFromArrayLike(
}
// static
MaybeHandle<Object> Object::GetLengthFromArrayLike(Isolate* isolate,
Handle<Object> object) {
Handle<Object> val;
Handle<Object> key = isolate->factory()->length_string();
ASSIGN_RETURN_ON_EXCEPTION(
isolate, val, Runtime::GetObjectProperty(isolate, object, key), Object);
return Object::ToLength(isolate, val);
}
// static
Maybe<bool> JSReceiver::HasProperty(LookupIterator* it) {
for (; it->IsFound(); it->Next()) {

View File

@ -1177,6 +1177,10 @@ class Object {
MUST_USE_RESULT static MaybeHandle<FixedArray> CreateListFromArrayLike(
Isolate* isolate, Handle<Object> object, ElementTypes element_types);
// Get length property and apply ToLength.
MUST_USE_RESULT static MaybeHandle<Object> GetLengthFromArrayLike(
Isolate* isolate, Handle<Object> object);
// Check whether |object| is an instance of Error or NativeError.
static bool IsErrorObject(Isolate* isolate, Handle<Object> object);

View File

@ -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);