enum Result { UNCHANGED, SUCCESS, EXCEPTION, CIRCULAR, STACK_OVERFLOW };
- template <bool is_ascii> void Extend();
+ void Extend();
void ChangeEncoding();
Handle<Object> ApplyToJsonFunction(Handle<Object> object,
Handle<Object> key);
+ Result SerializeGeneric(Handle<Object> object,
+ Handle<Object> key,
+ bool deferred_comma,
+ bool deferred_key);
+
// Entry point to serialize the object.
INLINE(Result SerializeObject(Handle<Object> obj)) {
- return Serialize_<false>(obj, false, isolate_->factory()->empty_string());
+ return Serialize_<false>(obj, false, factory_->empty_string());
}
// Serialize an array element.
void StackPop();
INLINE(Handle<String> accumulator()) {
- return Handle<String>(String::cast(accumulator_store_->value()));
+ return Handle<String>(String::cast(accumulator_store_->value()), isolate_);
}
INLINE(void set_accumulator(Handle<String> string)) {
}
Isolate* isolate_;
+ Factory* factory_;
// We use a value wrapper for the string accumulator to keep the
// (indirect) handle to it in the outermost handle scope.
Handle<JSValue> accumulator_store_;
BasicJsonStringifier::BasicJsonStringifier(Isolate* isolate)
: isolate_(isolate), current_index_(0), is_ascii_(true) {
+ factory_ = isolate_->factory();
accumulator_store_ = Handle<JSValue>::cast(
- isolate_->factory()->ToObject(isolate_->factory()->empty_string()));
+ factory_->ToObject(factory_->empty_string()));
part_length_ = kInitialPartLength;
- current_part_ =
- isolate_->factory()->NewRawAsciiString(kInitialPartLength);
- tojson_symbol_ = isolate_->factory()->LookupAsciiSymbol("toJSON");
- stack_ = isolate_->factory()->NewJSArray(8);
+ current_part_ = factory_->NewRawAsciiString(kInitialPartLength);
+ tojson_symbol_ = factory_->LookupAsciiSymbol("toJSON");
+ stack_ = factory_->NewJSArray(8);
}
return isolate_->heap()->undefined_value();
case SUCCESS:
ShrinkCurrentPart();
- return *isolate_->factory()->NewConsString(accumulator(), current_part_);
+ return *factory_->NewConsString(accumulator(), current_part_);
case CIRCULAR:
- return isolate_->Throw(*isolate_->factory()->NewTypeError(
+ return isolate_->Throw(*factory_->NewTypeError(
"circular_structure", HandleVector<Object>(NULL, 0)));
case STACK_OVERFLOW:
return isolate_->StackOverflow();
SeqTwoByteString::cast(*current_part_)->SeqTwoByteStringSet(
current_index_++, c);
}
- if (current_index_ == part_length_) Extend<is_ascii>();
+ if (current_index_ == part_length_) Extend();
}
Handle<String> key) {
LookupResult lookup(isolate_);
object->LocalLookupRealNamedProperty(*key, &lookup);
- if (!lookup.IsProperty()) return isolate_->factory()->undefined_value();
+ if (!lookup.IsProperty()) return factory_->undefined_value();
switch (lookup.type()) {
case NORMAL: {
Object* value = lookup.holder()->GetNormalizedProperty(&lookup);
ASSERT(!value->IsTheHole());
- return Handle<Object>(value);
+ return Handle<Object>(value, isolate_);
}
case FIELD: {
Object* value = lookup.holder()->FastPropertyAt(lookup.GetFieldIndex());
ASSERT(!value->IsTheHole());
- return Handle<Object>(value);
+ return Handle<Object>(value, isolate_);
}
case CONSTANT_FUNCTION:
- return Handle<Object>(lookup.GetConstantFunction());
+ return Handle<Object>(lookup.GetConstantFunction(), isolate_);
default: {
PropertyAttributes attr;
return Object::GetProperty(object, object, &lookup, key, &attr);
if (!fun->IsJSFunction()) return object;
// Call toJSON function.
- if (key->IsSmi()) key = isolate_->factory()->NumberToString(key);
+ if (key->IsSmi()) key = factory_->NumberToString(key);
Handle<Object> argv[] = { key };
bool has_exception = false;
HandleScope scope(isolate_);
if (object.is_null()) return EXCEPTION;
}
- if (object->IsJSObject()) {
- if (object->IsJSFunction()) return UNCHANGED;
+ if (object->IsSmi()) {
if (deferred_string_key) SerializeDeferredKey(comma, key);
- if (object->IsJSArray()) {
- return SerializeJSArray(Handle<JSArray>::cast(object));
- } else if (object->IsJSValue()) {
- return SerializeJSValue(Handle<JSValue>::cast(object));
- } else {
- return SerializeJSObject(Handle<JSObject>::cast(object));
- }
+ return SerializeSmi(Smi::cast(*object));
}
- // Handle non-JSObject.
- if (object->IsString()) {
- if (deferred_string_key) SerializeDeferredKey(comma, key);
- SerializeString(Handle<String>::cast(object));
- return SUCCESS;
- } else if (object->IsSmi()) {
- if (deferred_string_key) SerializeDeferredKey(comma, key);
- return SerializeSmi(Smi::cast(*object));
- } else if (object->IsHeapNumber()) {
- if (deferred_string_key) SerializeDeferredKey(comma, key);
- return SerializeHeapNumber(Handle<HeapNumber>::cast(object));
- } else if (object->IsOddball()) {
- switch (Oddball::cast(*object)->kind()) {
- case Oddball::kFalse:
- if (deferred_string_key) SerializeDeferredKey(comma, key);
- Append("false");
- return SUCCESS;
- case Oddball::kTrue:
+ switch (HeapObject::cast(*object)->map()->instance_type()) {
+ case HEAP_NUMBER_TYPE:
+ if (deferred_string_key) SerializeDeferredKey(comma, key);
+ return SerializeHeapNumber(Handle<HeapNumber>::cast(object));
+ case ODDBALL_TYPE:
+ switch (Oddball::cast(*object)->kind()) {
+ case Oddball::kFalse:
+ if (deferred_string_key) SerializeDeferredKey(comma, key);
+ Append("false");
+ return SUCCESS;
+ case Oddball::kTrue:
+ if (deferred_string_key) SerializeDeferredKey(comma, key);
+ Append("true");
+ return SUCCESS;
+ case Oddball::kNull:
+ if (deferred_string_key) SerializeDeferredKey(comma, key);
+ Append("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 JS_FUNCTION_TYPE:
+ return UNCHANGED;
+ default:
+ if (object->IsString()) {
if (deferred_string_key) SerializeDeferredKey(comma, key);
- Append("true");
+ SerializeString(Handle<String>::cast(object));
return SUCCESS;
- case Oddball::kNull:
+ } else if (object->IsJSObject()) {
if (deferred_string_key) SerializeDeferredKey(comma, key);
- Append("null");
- return SUCCESS;
- }
+ return SerializeJSObject(Handle<JSObject>::cast(object));
+ } else {
+ return SerializeGeneric(object, key, comma, deferred_string_key);
+ }
}
+}
+
- return UNCHANGED;
+BasicJsonStringifier::Result BasicJsonStringifier::SerializeGeneric(
+ Handle<Object> object,
+ Handle<Object> key,
+ bool deferred_comma,
+ bool deferred_key) {
+ Handle<JSObject> builtins(isolate_->native_context()->builtins());
+ Handle<JSFunction> builtin = Handle<JSFunction>::cast(
+ v8::internal::GetProperty(builtins, "JSONSerializeAdapter"));
+
+ Handle<Object> argv[] = { key, object };
+ bool has_exception = false;
+ Handle<Object> result =
+ Execution::Call(builtin, object, 2, argv, &has_exception);
+ if (has_exception) return EXCEPTION;
+ if (result->IsUndefined()) return UNCHANGED;
+ if (deferred_key) {
+ if (key->IsSmi()) key = factory_->NumberToString(key);
+ SerializeDeferredKey(deferred_comma, key);
+ }
+
+ Handle<String> result_string = Handle<String>::cast(result);
+ // Shrink current part, attach it to the accumulator, also attach the result
+ // string to the accumulator, and allocate a new part.
+ ShrinkCurrentPart(); // Shrink.
+ part_length_ = kInitialPartLength; // Allocate conservatively.
+ Extend(); // Attach current part and allocate new part.
+ // Attach result string to the accumulator.
+ set_accumulator(factory_->NewConsString(accumulator(), result_string));
+ return SUCCESS;
}
Append('[');
switch (object->GetElementsKind()) {
case FAST_SMI_ELEMENTS: {
- Handle<FixedArray> elements(FixedArray::cast(object->elements()));
+ Handle<FixedArray> elements(
+ FixedArray::cast(object->elements()), isolate_);
for (int i = 0; i < length; i++) {
if (i > 0) Append(',');
SerializeSmi(Smi::cast(elements->get(i)));
}
case FAST_DOUBLE_ELEMENTS: {
Handle<FixedDoubleArray> elements(
- FixedDoubleArray::cast(object->elements()));
+ FixedDoubleArray::cast(object->elements()), isolate_);
for (int i = 0; i < length; i++) {
if (i > 0) Append(',');
SerializeDouble(elements->get_scalar(i));
break;
}
case FAST_ELEMENTS: {
- Handle<FixedArray> elements(FixedArray::cast(object->elements()));
+ Handle<FixedArray> elements(
+ FixedArray::cast(object->elements()), isolate_);
for (int i = 0; i < length; i++) {
if (i > 0) Append(',');
- Result result = SerializeElement(Handle<Object>(elements->get(i)), i);
+ Result result =
+ SerializeElement(Handle<Object>(elements->get(i), isolate_), i);
if (result == SUCCESS) continue;
if (result == UNCHANGED) {
Append("null");
Result stack_push = StackPush(object);
if (stack_push != SUCCESS) return stack_push;
if (object->IsJSGlobalProxy()) {
- object = Handle<JSObject>(JSObject::cast(object->GetPrototype()));
+ object = Handle<JSObject>(
+ JSObject::cast(object->GetPrototype()), isolate_);
ASSERT(object->IsGlobalObject());
}
bool has_exception = false;
Handle<String> key_handle;
Handle<Object> property;
if (key->IsString()) {
- key_handle = Handle<String>(String::cast(key));
+ key_handle = Handle<String>(String::cast(key), isolate_);
property = GetProperty(object, key_handle);
} else {
ASSERT(key->IsNumber());
- key_handle = isolate_->factory()->NumberToString(Handle<Object>(key));
+ key_handle = factory_->NumberToString(Handle<Object>(key, isolate_));
uint32_t index;
if (key->IsSmi()) {
property = Object::GetElement(object, Smi::cast(key)->value());
void BasicJsonStringifier::ShrinkCurrentPart() {
ASSERT(current_index_ < part_length_);
if (current_index_ == 0) {
- current_part_ = isolate_->factory()->empty_string();
+ current_part_ = factory_->empty_string();
return;
}
}
-template <bool is_ascii>
void BasicJsonStringifier::Extend() {
- set_accumulator(
- isolate_->factory()->NewConsString(accumulator(), current_part_));
+ set_accumulator(factory_->NewConsString(accumulator(), current_part_));
if (part_length_ <= kMaxPartLength / kPartLengthGrowthFactor) {
part_length_ *= kPartLengthGrowthFactor;
}
- if (is_ascii) {
- current_part_ =
- isolate_->factory()->NewRawAsciiString(part_length_);
+ if (is_ascii_) {
+ current_part_ = factory_->NewRawAsciiString(part_length_);
} else {
- current_part_ =
- isolate_->factory()->NewRawTwoByteString(part_length_);
+ current_part_ = factory_->NewRawTwoByteString(part_length_);
}
current_index_ = 0;
}
void BasicJsonStringifier::ChangeEncoding() {
ShrinkCurrentPart();
- set_accumulator(
- isolate_->factory()->NewConsString(accumulator(), current_part_));
- current_part_ =
- isolate_->factory()->NewRawTwoByteString(part_length_);
+ set_accumulator(factory_->NewConsString(accumulator(), current_part_));
+ current_part_ = factory_->NewRawTwoByteString(part_length_);
current_index_ = 0;
is_ascii_ = false;
}
--- /dev/null
+// Copyright 2012 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Flags: --harmony
+
+function testStringify(expected, object) {
+ // Test fast case that bails out to slow case.
+ assertEquals(expected, JSON.stringify(object));
+ // Test slow case.
+ assertEquals(expected, JSON.stringify(object, undefined, 0));
+}
+
+// Test serializing a proxy, function proxy and objects that contain them.
+var handler1 = {
+ get: function(target, name) {
+ return name.toUpperCase();
+ },
+ enumerate: function(target) {
+ return ['a', 'b', 'c'];
+ },
+ getOwnPropertyDescriptor: function(target, name) {
+ return { enumerable: true };
+ }
+}
+
+var proxy1 = Proxy.create(handler1);
+testStringify('{"a":"A","b":"B","c":"C"}', proxy1);
+
+var proxy_fun = Proxy.createFunction(handler1, function() { return 1; });
+testStringify(undefined, proxy_fun);
+testStringify('[1,null]', [1, proxy_fun]);
+
+var parent1a = { b: proxy1 };
+testStringify('{"b":{"a":"A","b":"B","c":"C"}}', parent1a);
+
+var parent1b = { a: 123, b: proxy1, c: true };
+testStringify('{"a":123,"b":{"a":"A","b":"B","c":"C"},"c":true}', parent1b);
+
+var parent1c = [123, proxy1, true];
+testStringify('[123,{"a":"A","b":"B","c":"C"},true]', parent1c);
+
+// Proxy with side effect.
+var handler2 = {
+ get: function(target, name) {
+ delete parent2.c;
+ return name.toUpperCase();
+ },
+ enumerate: function(target) {
+ return ['a', 'b', 'c'];
+ },
+ getOwnPropertyDescriptor: function(target, name) {
+ return { enumerable: true };
+ }
+}
+
+var proxy2 = Proxy.create(handler2);
+var parent2 = { a: "delete", b: proxy2, c: "remove" };
+var expected2 = '{"a":"delete","b":{"a":"A","b":"B","c":"C"}}';
+assertEquals(expected2, JSON.stringify(parent2));
+parent2.c = "remove"; // Revert side effect.
+assertEquals(expected2, JSON.stringify(parent2, undefined, 0));
+
+// Proxy with a get function that uses the first argument.
+var handler3 = {
+ get: function(target, name) {
+ if (name == 'valueOf') return function() { return "proxy" };
+ return name + "(" + target + ")";
+ },
+ enumerate: function(target) {
+ return ['a', 'b', 'c'];
+ },
+ getOwnPropertyDescriptor: function(target, name) {
+ return { enumerable: true };
+ }
+}
+
+var proxy3 = Proxy.create(handler3);
+var parent3 = { x: 123, y: proxy3 }
+testStringify('{"x":123,"y":{"a":"a(proxy)","b":"b(proxy)","c":"c(proxy)"}}',
+ parent3);
+
+// Empty proxy.
+var handler4 = {
+ get: function(target, name) {
+ return 0;
+ },
+ enumerate: function(target) {
+ return [];
+ },
+ getOwnPropertyDescriptor: function(target, name) {
+ return { enumerable: false };
+ }
+}
+
+var proxy4 = Proxy.create(handler4);
+testStringify('{}', proxy4);
+testStringify('{"a":{}}', { a: proxy4 });
+
+// Proxy that provides a toJSON function that uses this.
+var handler5 = {
+ get: function(target, name) {
+ if (name == 'z') return 97000;
+ return function(key) { return key.charCodeAt(0) + this.z; };
+ },
+ enumerate: function(target) {
+ return ['toJSON', 'z'];
+ },
+ getOwnPropertyDescriptor: function(target, name) {
+ return { enumerable: true };
+ }
+}
+
+var proxy5 = Proxy.create(handler5);
+testStringify('{"a":97097}', { a: proxy5 });
+
+// Proxy that provides a toJSON function that returns undefined.
+var handler6 = {
+ get: function(target, name) {
+ return function(key) { return undefined; };
+ },
+ enumerate: function(target) {
+ return ['toJSON'];
+ },
+ getOwnPropertyDescriptor: function(target, name) {
+ return { enumerable: true };
+ }
+}
+
+var proxy6 = Proxy.create(handler6);
+testStringify('[1,null,true]', [1, proxy6, true]);
+testStringify('{"a":1,"c":true}', {a: 1, b: proxy6, c: true});
+