Handle edge cases in basic JSON.stringify.
BUG= Review URL: https://chromiumcodereview.appspot.com/11315009 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@12842 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
64793b3f0d
commit
e8d91b424c
@ -46,7 +46,7 @@ class BasicJsonStringifier BASE_EMBEDDED {
|
|||||||
static const int kMaxPartLength = 16 * 1024;
|
static const int kMaxPartLength = 16 * 1024;
|
||||||
static const int kPartLengthGrowthFactor = 2;
|
static const int kPartLengthGrowthFactor = 2;
|
||||||
|
|
||||||
enum Result { UNCHANGED, SUCCESS, BAILOUT, CIRCULAR, STACK_OVERFLOW };
|
enum Result { UNCHANGED, SUCCESS, EXCEPTION, CIRCULAR, STACK_OVERFLOW };
|
||||||
|
|
||||||
template <bool is_ascii> void Extend();
|
template <bool is_ascii> void Extend();
|
||||||
|
|
||||||
@ -79,27 +79,36 @@ class BasicJsonStringifier BASE_EMBEDDED {
|
|||||||
Handle<Object> GetProperty(Handle<JSObject> object,
|
Handle<Object> GetProperty(Handle<JSObject> object,
|
||||||
Handle<String> key);
|
Handle<String> key);
|
||||||
|
|
||||||
bool MayHaveToJsonFunction(Handle<JSObject> object);
|
Handle<Object> ApplyToJsonFunction(Handle<Object> object,
|
||||||
|
Handle<Object> key);
|
||||||
|
|
||||||
INLINE(Result Serialize(Handle<Object> object)) {
|
// Entry point to serialize the object.
|
||||||
return Serialize_<false>(object);
|
INLINE(Result SerializeObject(Handle<Object> obj)) {
|
||||||
|
return Serialize_<false>(obj, false, isolate_->factory()->empty_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
INLINE(Result SerializeDeferred(Handle<Object> object,
|
// Serialize an array element.
|
||||||
|
// The index may serve as argument for the toJSON function.
|
||||||
|
INLINE(Result SerializeElement(Handle<Object> object, int i)) {
|
||||||
|
return Serialize_<false>(object, false, Handle<Object>(Smi::FromInt(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize a object property.
|
||||||
|
// The key may or may not be serialized depending on the property.
|
||||||
|
// The key may also serve as argument for the toJSON function.
|
||||||
|
INLINE(Result SerializeProperty(Handle<Object> object,
|
||||||
bool deferred_comma,
|
bool deferred_comma,
|
||||||
Handle<String> deferred_key)) {
|
Handle<String> deferred_key)) {
|
||||||
ASSERT(!deferred_key.is_null());
|
ASSERT(!deferred_key.is_null());
|
||||||
return Serialize_<true>(object, deferred_comma, deferred_key);
|
return Serialize_<true>(object, deferred_comma, deferred_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool deferred_key>
|
template <bool deferred_string_key>
|
||||||
Result Serialize_(Handle<Object> object,
|
Result Serialize_(Handle<Object> object, bool comma, Handle<Object> key);
|
||||||
bool comma = false,
|
|
||||||
Handle<String> key = Handle<String>::null());
|
|
||||||
|
|
||||||
void SerializeDeferredKey(bool deferred_comma, Handle<String> deferred_key) {
|
void SerializeDeferredKey(bool deferred_comma, Handle<Object> deferred_key) {
|
||||||
if (deferred_comma) Append(',');
|
if (deferred_comma) Append(',');
|
||||||
SerializeString(deferred_key);
|
SerializeString(Handle<String>::cast(deferred_key));
|
||||||
Append(':');
|
Append(':');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,8 +119,12 @@ class BasicJsonStringifier BASE_EMBEDDED {
|
|||||||
return SerializeDouble(object->value());
|
return SerializeDouble(object->value());
|
||||||
}
|
}
|
||||||
|
|
||||||
INLINE(Result SerializeArray(Handle<JSArray> object));
|
Result SerializeJSValue(Handle<JSValue> object);
|
||||||
INLINE(Result SerializeObject(Handle<JSObject> object));
|
|
||||||
|
INLINE(Result SerializeJSArray(Handle<JSArray> object));
|
||||||
|
INLINE(Result SerializeJSObject(Handle<JSObject> object));
|
||||||
|
|
||||||
|
Result SerializeJSArraySlow(Handle<JSArray> object, int length);
|
||||||
|
|
||||||
void SerializeString(Handle<String> object);
|
void SerializeString(Handle<String> object);
|
||||||
|
|
||||||
@ -207,19 +220,19 @@ BasicJsonStringifier::BasicJsonStringifier(Isolate* isolate)
|
|||||||
|
|
||||||
|
|
||||||
MaybeObject* BasicJsonStringifier::Stringify(Handle<Object> object) {
|
MaybeObject* BasicJsonStringifier::Stringify(Handle<Object> object) {
|
||||||
switch (Serialize(object)) {
|
switch (SerializeObject(object)) {
|
||||||
|
case UNCHANGED:
|
||||||
|
return isolate_->heap()->undefined_value();
|
||||||
case SUCCESS:
|
case SUCCESS:
|
||||||
ShrinkCurrentPart();
|
ShrinkCurrentPart();
|
||||||
return *isolate_->factory()->NewConsString(accumulator(), current_part_);
|
return *isolate_->factory()->NewConsString(accumulator(), current_part_);
|
||||||
case UNCHANGED:
|
|
||||||
return isolate_->heap()->undefined_value();
|
|
||||||
case CIRCULAR:
|
case CIRCULAR:
|
||||||
return isolate_->Throw(*isolate_->factory()->NewTypeError(
|
return isolate_->Throw(*isolate_->factory()->NewTypeError(
|
||||||
"circular_structure", HandleVector<Object>(NULL, 0)));
|
"circular_structure", HandleVector<Object>(NULL, 0)));
|
||||||
case STACK_OVERFLOW:
|
case STACK_OVERFLOW:
|
||||||
return isolate_->StackOverflow();
|
return isolate_->StackOverflow();
|
||||||
default:
|
default:
|
||||||
return Smi::FromInt(0);
|
return Failure::Exception();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,36 +274,34 @@ Handle<Object> BasicJsonStringifier::GetProperty(Handle<JSObject> object,
|
|||||||
}
|
}
|
||||||
case CONSTANT_FUNCTION:
|
case CONSTANT_FUNCTION:
|
||||||
return Handle<Object>(lookup.GetConstantFunction());
|
return Handle<Object>(lookup.GetConstantFunction());
|
||||||
case CALLBACKS:
|
default: {
|
||||||
case HANDLER:
|
PropertyAttributes attr;
|
||||||
case INTERCEPTOR:
|
return Object::GetProperty(object, object, &lookup, key, &attr);
|
||||||
return Handle<Object>::null();
|
}
|
||||||
case TRANSITION:
|
|
||||||
case NONEXISTENT:
|
|
||||||
UNREACHABLE();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return Handle<Object>::null();
|
return Handle<Object>::null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool BasicJsonStringifier::MayHaveToJsonFunction(Handle<JSObject> object) {
|
Handle<Object> BasicJsonStringifier::ApplyToJsonFunction(
|
||||||
|
Handle<Object> object, Handle<Object> key) {
|
||||||
LookupResult lookup(isolate_);
|
LookupResult lookup(isolate_);
|
||||||
object->LookupRealNamedProperty(*tojson_symbol_, &lookup);
|
JSObject::cast(*object)->LookupRealNamedProperty(*tojson_symbol_, &lookup);
|
||||||
if (!lookup.IsProperty()) return false;
|
if (!lookup.IsProperty()) return object;
|
||||||
Object* value;
|
PropertyAttributes attr;
|
||||||
switch (lookup.type()) {
|
Handle<Object> fun =
|
||||||
case NORMAL:
|
Object::GetProperty(object, object, &lookup, tojson_symbol_, &attr);
|
||||||
value = lookup.holder()->GetNormalizedProperty(&lookup);
|
if (!fun->IsJSFunction()) return object;
|
||||||
break;
|
|
||||||
case FIELD:
|
// Call toJSON function.
|
||||||
value = lookup.holder()->FastPropertyAt(lookup.GetFieldIndex());
|
if (key->IsSmi()) key = isolate_->factory()->NumberToString(key);
|
||||||
break;
|
Handle<Object> argv[] = { key };
|
||||||
default:
|
bool has_exception = false;
|
||||||
return true;
|
HandleScope scope(isolate_);
|
||||||
}
|
object = Execution::Call(fun, object, 1, argv, &has_exception);
|
||||||
ASSERT(!value->IsTheHole());
|
// Return empty handle to signal an exception.
|
||||||
return value->IsSpecFunction();
|
if (has_exception) return Handle<Object>::null();
|
||||||
|
return scope.CloseAndEscape(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -319,51 +330,49 @@ void BasicJsonStringifier::StackPop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
template <bool deferred_key>
|
template <bool deferred_string_key>
|
||||||
BasicJsonStringifier::Result BasicJsonStringifier::Serialize_(
|
BasicJsonStringifier::Result BasicJsonStringifier::Serialize_(
|
||||||
Handle<Object> object, bool comma, Handle<String> key) {
|
Handle<Object> object, bool comma, Handle<Object> key) {
|
||||||
if (object->IsJSObject()) {
|
if (object->IsJSObject()) {
|
||||||
// We don't deal with custom toJSON functions.
|
object = ApplyToJsonFunction(object, key);
|
||||||
if (MayHaveToJsonFunction(Handle<JSObject>::cast(object))) return BAILOUT;
|
if (object.is_null()) return EXCEPTION;
|
||||||
|
}
|
||||||
|
|
||||||
if (object->IsJSFunction()) {
|
if (object->IsJSObject()) {
|
||||||
return UNCHANGED;
|
if (object->IsJSFunction()) return UNCHANGED;
|
||||||
} else if (object->IsJSArray()) {
|
if (deferred_string_key) SerializeDeferredKey(comma, key);
|
||||||
if (deferred_key) SerializeDeferredKey(comma, key);
|
if (object->IsJSArray()) {
|
||||||
return SerializeArray(Handle<JSArray>::cast(object));
|
return SerializeJSArray(Handle<JSArray>::cast(object));
|
||||||
} else if (object->IsJSValue()) {
|
} else if (object->IsJSValue()) {
|
||||||
// JSValue with a custom prototype.
|
return SerializeJSValue(Handle<JSValue>::cast(object));
|
||||||
if (object->GetPrototype()->IsJSReceiver()) return BAILOUT;
|
|
||||||
// Unpack value wrapper and fall through.
|
|
||||||
object = Handle<Object>(JSValue::cast(*object)->value());
|
|
||||||
} else {
|
} else {
|
||||||
if (deferred_key) SerializeDeferredKey(comma, key);
|
return SerializeJSObject(Handle<JSObject>::cast(object));
|
||||||
return SerializeObject(Handle<JSObject>::cast(object));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle non-JSObject.
|
||||||
if (object->IsString()) {
|
if (object->IsString()) {
|
||||||
if (deferred_key) SerializeDeferredKey(comma, key);
|
if (deferred_string_key) SerializeDeferredKey(comma, key);
|
||||||
SerializeString(Handle<String>::cast(object));
|
SerializeString(Handle<String>::cast(object));
|
||||||
return SUCCESS;
|
return SUCCESS;
|
||||||
} else if (object->IsSmi()) {
|
} else if (object->IsSmi()) {
|
||||||
if (deferred_key) SerializeDeferredKey(comma, key);
|
if (deferred_string_key) SerializeDeferredKey(comma, key);
|
||||||
return SerializeSmi(Smi::cast(*object));
|
return SerializeSmi(Smi::cast(*object));
|
||||||
} else if (object->IsHeapNumber()) {
|
} else if (object->IsHeapNumber()) {
|
||||||
if (deferred_key) SerializeDeferredKey(comma, key);
|
if (deferred_string_key) SerializeDeferredKey(comma, key);
|
||||||
return SerializeHeapNumber(Handle<HeapNumber>::cast(object));
|
return SerializeHeapNumber(Handle<HeapNumber>::cast(object));
|
||||||
} else if (object->IsOddball()) {
|
} else if (object->IsOddball()) {
|
||||||
switch (Oddball::cast(*object)->kind()) {
|
switch (Oddball::cast(*object)->kind()) {
|
||||||
case Oddball::kFalse:
|
case Oddball::kFalse:
|
||||||
if (deferred_key) SerializeDeferredKey(comma, key);
|
if (deferred_string_key) SerializeDeferredKey(comma, key);
|
||||||
Append("false");
|
Append("false");
|
||||||
return SUCCESS;
|
return SUCCESS;
|
||||||
case Oddball::kTrue:
|
case Oddball::kTrue:
|
||||||
if (deferred_key) SerializeDeferredKey(comma, key);
|
if (deferred_string_key) SerializeDeferredKey(comma, key);
|
||||||
Append("true");
|
Append("true");
|
||||||
return SUCCESS;
|
return SUCCESS;
|
||||||
case Oddball::kNull:
|
case Oddball::kNull:
|
||||||
if (deferred_key) SerializeDeferredKey(comma, key);
|
if (deferred_string_key) SerializeDeferredKey(comma, key);
|
||||||
Append("null");
|
Append("null");
|
||||||
return SUCCESS;
|
return SUCCESS;
|
||||||
}
|
}
|
||||||
@ -373,6 +382,29 @@ BasicJsonStringifier::Result BasicJsonStringifier::Serialize_(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSValue(
|
||||||
|
Handle<JSValue> object) {
|
||||||
|
bool has_exception = false;
|
||||||
|
String* class_name = object->class_name();
|
||||||
|
if (class_name == isolate_->heap()->String_symbol()) {
|
||||||
|
Handle<Object> value = Execution::ToString(object, &has_exception);
|
||||||
|
if (has_exception) return EXCEPTION;
|
||||||
|
SerializeString(Handle<String>::cast(value));
|
||||||
|
} else if (class_name == isolate_->heap()->Number_symbol()) {
|
||||||
|
Handle<Object> value = Execution::ToNumber(object, &has_exception);
|
||||||
|
if (has_exception) return EXCEPTION;
|
||||||
|
if (value->IsSmi()) return SerializeSmi(Smi::cast(*value));
|
||||||
|
SerializeHeapNumber(Handle<HeapNumber>::cast(value));
|
||||||
|
} else {
|
||||||
|
ASSERT(class_name == isolate_->heap()->Boolean_symbol());
|
||||||
|
Object* value = JSValue::cast(*object)->value();
|
||||||
|
ASSERT(value->IsBoolean());
|
||||||
|
Append(value->IsTrue() ? "true" : "false");
|
||||||
|
}
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
BasicJsonStringifier::Result BasicJsonStringifier::SerializeSmi(Smi* object) {
|
BasicJsonStringifier::Result BasicJsonStringifier::SerializeSmi(Smi* object) {
|
||||||
static const int kBufferSize = 100;
|
static const int kBufferSize = 100;
|
||||||
char chars[kBufferSize];
|
char chars[kBufferSize];
|
||||||
@ -396,7 +428,7 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeDouble(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
BasicJsonStringifier::Result BasicJsonStringifier::SerializeArray(
|
BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSArray(
|
||||||
Handle<JSArray> object) {
|
Handle<JSArray> object) {
|
||||||
HandleScope handle_scope(isolate_);
|
HandleScope handle_scope(isolate_);
|
||||||
Result stack_push = StackPush(object);
|
Result stack_push = StackPush(object);
|
||||||
@ -405,8 +437,7 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeArray(
|
|||||||
Append('[');
|
Append('[');
|
||||||
switch (object->GetElementsKind()) {
|
switch (object->GetElementsKind()) {
|
||||||
case FAST_SMI_ELEMENTS: {
|
case FAST_SMI_ELEMENTS: {
|
||||||
Handle<FixedArray> elements = Handle<FixedArray>(
|
Handle<FixedArray> elements(FixedArray::cast(object->elements()));
|
||||||
FixedArray::cast(object->elements()));
|
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
if (i > 0) Append(',');
|
if (i > 0) Append(',');
|
||||||
SerializeSmi(Smi::cast(elements->get(i)));
|
SerializeSmi(Smi::cast(elements->get(i)));
|
||||||
@ -414,7 +445,7 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeArray(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FAST_DOUBLE_ELEMENTS: {
|
case FAST_DOUBLE_ELEMENTS: {
|
||||||
Handle<FixedDoubleArray> elements = Handle<FixedDoubleArray>(
|
Handle<FixedDoubleArray> elements(
|
||||||
FixedDoubleArray::cast(object->elements()));
|
FixedDoubleArray::cast(object->elements()));
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
if (i > 0) Append(',');
|
if (i > 0) Append(',');
|
||||||
@ -423,11 +454,10 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeArray(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FAST_ELEMENTS: {
|
case FAST_ELEMENTS: {
|
||||||
Handle<FixedArray> elements = Handle<FixedArray>(
|
Handle<FixedArray> elements(FixedArray::cast(object->elements()));
|
||||||
FixedArray::cast(object->elements()));
|
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
if (i > 0) Append(',');
|
if (i > 0) Append(',');
|
||||||
Result result = Serialize(Handle<Object>(elements->get(i)));
|
Result result = SerializeElement(Handle<Object>(elements->get(i)), i);
|
||||||
if (result == SUCCESS) continue;
|
if (result == SUCCESS) continue;
|
||||||
if (result == UNCHANGED) {
|
if (result == UNCHANGED) {
|
||||||
Append("null");
|
Append("null");
|
||||||
@ -437,8 +467,14 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeArray(
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
// TODO(yangguo): The FAST_HOLEY_* cases could be handled in a faster way.
|
||||||
return BAILOUT;
|
// They resemble the non-holey cases except that a prototype chain lookup
|
||||||
|
// is necessary for holes.
|
||||||
|
default: {
|
||||||
|
Result result = SerializeJSArraySlow(object, length);
|
||||||
|
if (result != SUCCESS) return result;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Append(']');
|
Append(']');
|
||||||
StackPop();
|
StackPop();
|
||||||
@ -447,16 +483,40 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeArray(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
BasicJsonStringifier::Result BasicJsonStringifier::SerializeObject(
|
BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSArraySlow(
|
||||||
|
Handle<JSArray> object, int length) {
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
if (i > 0) Append(',');
|
||||||
|
Handle<Object> element = Object::GetElement(object, i);
|
||||||
|
if (element->IsUndefined()) {
|
||||||
|
Append("null");
|
||||||
|
} else {
|
||||||
|
Result result = SerializeElement(element, i);
|
||||||
|
if (result == SUCCESS) continue;
|
||||||
|
if (result == UNCHANGED) {
|
||||||
|
Append("null");
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasicJsonStringifier::Result BasicJsonStringifier::SerializeJSObject(
|
||||||
Handle<JSObject> object) {
|
Handle<JSObject> object) {
|
||||||
HandleScope handle_scope(isolate_);
|
HandleScope handle_scope(isolate_);
|
||||||
Result stack_push = StackPush(object);
|
Result stack_push = StackPush(object);
|
||||||
if (stack_push != SUCCESS) return stack_push;
|
if (stack_push != SUCCESS) return stack_push;
|
||||||
if (object->IsJSGlobalProxy()) return BAILOUT;
|
if (object->IsJSGlobalProxy()) {
|
||||||
bool threw = false;
|
object = Handle<JSObject>(JSObject::cast(object->GetPrototype()));
|
||||||
|
ASSERT(object->IsGlobalObject());
|
||||||
|
}
|
||||||
|
bool has_exception = false;
|
||||||
Handle<FixedArray> contents =
|
Handle<FixedArray> contents =
|
||||||
GetKeysInFixedArrayFor(object, LOCAL_ONLY, &threw);
|
GetKeysInFixedArrayFor(object, LOCAL_ONLY, &has_exception);
|
||||||
if (threw) return BAILOUT;
|
if (has_exception) return EXCEPTION;
|
||||||
Append('{');
|
Append('{');
|
||||||
bool comma = false;
|
bool comma = false;
|
||||||
for (int i = 0; i < contents->length(); i++) {
|
for (int i = 0; i < contents->length(); i++) {
|
||||||
@ -478,10 +538,10 @@ BasicJsonStringifier::Result BasicJsonStringifier::SerializeObject(
|
|||||||
property = GetProperty(object, key_handle);
|
property = GetProperty(object, key_handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (property.is_null()) return BAILOUT;
|
if (property.is_null()) return EXCEPTION;
|
||||||
Result result = SerializeDeferred(property, comma, key_handle);
|
Result result = SerializeProperty(property, comma, key_handle);
|
||||||
if (!comma && result == SUCCESS) comma = true;
|
if (!comma && result == SUCCESS) comma = true;
|
||||||
if (result >= BAILOUT) return result;
|
if (result >= EXCEPTION) return result;
|
||||||
}
|
}
|
||||||
Append('}');
|
Append('}');
|
||||||
StackPop();
|
StackPop();
|
||||||
|
137
src/json.js
137
src/json.js
@ -178,143 +178,9 @@ function JSONSerialize(key, holder, replacer, stack, indent, gap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function BasicSerializeArray(value, stack, builder) {
|
|
||||||
var len = value.length;
|
|
||||||
if (len == 0) {
|
|
||||||
builder.push("[]");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!%PushIfAbsent(stack, value)) {
|
|
||||||
throw MakeTypeError('circular_structure', $Array());
|
|
||||||
}
|
|
||||||
builder.push("[");
|
|
||||||
var val = value[0];
|
|
||||||
if (IS_STRING(val)) {
|
|
||||||
// First entry is a string. Remaining entries are likely to be strings too.
|
|
||||||
var array_string = %QuoteJSONStringArray(value);
|
|
||||||
if (!IS_UNDEFINED(array_string)) {
|
|
||||||
// array_string also includes bracket characters so we are done.
|
|
||||||
builder[builder.length - 1] = array_string;
|
|
||||||
stack.pop();
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
builder.push(%QuoteJSONString(val));
|
|
||||||
for (var i = 1; i < len; i++) {
|
|
||||||
val = value[i];
|
|
||||||
if (IS_STRING(val)) {
|
|
||||||
builder.push(%QuoteJSONStringComma(val));
|
|
||||||
} else {
|
|
||||||
builder.push(",");
|
|
||||||
var before = builder.length;
|
|
||||||
BasicJSONSerialize(i, val, stack, builder);
|
|
||||||
if (before == builder.length) builder[before - 1] = ",null";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (IS_NUMBER(val)) {
|
|
||||||
// First entry is a number. Remaining entries are likely to be numbers too.
|
|
||||||
builder.push(JSON_NUMBER_TO_STRING(val));
|
|
||||||
for (var i = 1; i < len; i++) {
|
|
||||||
builder.push(",");
|
|
||||||
val = value[i];
|
|
||||||
if (IS_NUMBER(val)) {
|
|
||||||
builder.push(JSON_NUMBER_TO_STRING(val));
|
|
||||||
} else {
|
|
||||||
var before = builder.length;
|
|
||||||
BasicJSONSerialize(i, val, stack, builder);
|
|
||||||
if (before == builder.length) builder[before - 1] = ",null";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var before = builder.length;
|
|
||||||
BasicJSONSerialize(0, val, stack, builder);
|
|
||||||
if (before == builder.length) builder.push("null");
|
|
||||||
for (var i = 1; i < len; i++) {
|
|
||||||
builder.push(",");
|
|
||||||
before = builder.length;
|
|
||||||
BasicJSONSerialize(i, value[i], stack, builder);
|
|
||||||
if (before == builder.length) builder[before - 1] = ",null";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stack.pop();
|
|
||||||
builder.push("]");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function BasicSerializeObject(value, stack, builder) {
|
|
||||||
if (!%PushIfAbsent(stack, value)) {
|
|
||||||
throw MakeTypeError('circular_structure', $Array());
|
|
||||||
}
|
|
||||||
builder.push("{");
|
|
||||||
var first = true;
|
|
||||||
var keys = %ObjectKeys(value);
|
|
||||||
var len = keys.length;
|
|
||||||
for (var i = 0; i < len; i++) {
|
|
||||||
var p = keys[i];
|
|
||||||
if (!first) {
|
|
||||||
builder.push(%QuoteJSONStringComma(p));
|
|
||||||
} else {
|
|
||||||
builder.push(%QuoteJSONString(p));
|
|
||||||
}
|
|
||||||
builder.push(":");
|
|
||||||
var before = builder.length;
|
|
||||||
BasicJSONSerialize(p, value[p], stack, builder);
|
|
||||||
if (before == builder.length) {
|
|
||||||
builder.pop();
|
|
||||||
builder.pop();
|
|
||||||
} else {
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stack.pop();
|
|
||||||
builder.push("}");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function BasicJSONSerialize(key, value, stack, builder) {
|
|
||||||
if (IS_SPEC_OBJECT(value)) {
|
|
||||||
var toJSON = value.toJSON;
|
|
||||||
if (IS_SPEC_FUNCTION(toJSON)) {
|
|
||||||
value = %_CallFunction(value, ToString(key), toJSON);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (IS_STRING(value)) {
|
|
||||||
builder.push(value !== "" ? %QuoteJSONString(value) : '""');
|
|
||||||
} else if (IS_NUMBER(value)) {
|
|
||||||
builder.push(JSON_NUMBER_TO_STRING(value));
|
|
||||||
} else if (IS_BOOLEAN(value)) {
|
|
||||||
builder.push(value ? "true" : "false");
|
|
||||||
} else if (IS_NULL(value)) {
|
|
||||||
builder.push("null");
|
|
||||||
} else if (IS_SPEC_OBJECT(value) && !(typeof value == "function")) {
|
|
||||||
// Value is a non-callable object.
|
|
||||||
// Unwrap value if necessary
|
|
||||||
if (IS_NUMBER_WRAPPER(value)) {
|
|
||||||
value = ToNumber(value);
|
|
||||||
builder.push(JSON_NUMBER_TO_STRING(value));
|
|
||||||
} else if (IS_STRING_WRAPPER(value)) {
|
|
||||||
builder.push(%QuoteJSONString(ToString(value)));
|
|
||||||
} else if (IS_BOOLEAN_WRAPPER(value)) {
|
|
||||||
builder.push(%_ValueOf(value) ? "true" : "false");
|
|
||||||
} else if (IS_ARRAY(value)) {
|
|
||||||
BasicSerializeArray(value, stack, builder);
|
|
||||||
} else {
|
|
||||||
BasicSerializeObject(value, stack, builder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function JSONStringify(value, replacer, space) {
|
function JSONStringify(value, replacer, space) {
|
||||||
if (%_ArgumentsLength() == 1) {
|
if (%_ArgumentsLength() == 1) {
|
||||||
var result = %BasicJSONStringify(value);
|
return %BasicJSONStringify(value);
|
||||||
if (result != 0) return result;
|
|
||||||
var builder = new InternalArray();
|
|
||||||
BasicJSONSerialize('', value, new InternalArray(), builder);
|
|
||||||
if (builder.length == 0) return;
|
|
||||||
result = %_FastAsciiArrayJoin(builder, "");
|
|
||||||
if (!IS_UNDEFINED(result)) return result;
|
|
||||||
return %StringBuilderConcat(builder, builder.length, "");
|
|
||||||
}
|
}
|
||||||
if (IS_OBJECT(space)) {
|
if (IS_OBJECT(space)) {
|
||||||
// Unwrap 'space' if it is wrapped
|
// Unwrap 'space' if it is wrapped
|
||||||
@ -340,6 +206,7 @@ function JSONStringify(value, replacer, space) {
|
|||||||
return JSONSerialize('', {'': value}, replacer, new InternalArray(), "", gap);
|
return JSONSerialize('', {'': value}, replacer, new InternalArray(), "", gap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function SetUpJSON() {
|
function SetUpJSON() {
|
||||||
%CheckIsBootstrapping();
|
%CheckIsBootstrapping();
|
||||||
InstallFunctions($JSON, DONT_ENUM, $Array(
|
InstallFunctions($JSON, DONT_ENUM, $Array(
|
||||||
|
@ -27,6 +27,89 @@
|
|||||||
|
|
||||||
// Flags: --allow-natives-syntax
|
// Flags: --allow-natives-syntax
|
||||||
|
|
||||||
|
// Test JSON.stringify on the global object.
|
||||||
|
var a = 12345;
|
||||||
|
assertTrue(JSON.stringify(this).indexOf('"a":12345') > 0);
|
||||||
|
|
||||||
|
// Test JSON.stringify of array in dictionary mode.
|
||||||
|
var array_1 = [];
|
||||||
|
var array_2 = [];
|
||||||
|
array_1[100000] = 1;
|
||||||
|
array_2[100000] = function() { return 1; };
|
||||||
|
var nulls = "";
|
||||||
|
for (var i = 0; i < 100000; i++) {
|
||||||
|
nulls += 'null,';
|
||||||
|
}
|
||||||
|
expected_1 = '[' + nulls + '1]';
|
||||||
|
expected_2 = '[' + nulls + 'null]';
|
||||||
|
assertEquals(expected_1, JSON.stringify(array_1));
|
||||||
|
assertEquals(expected_2, JSON.stringify(array_2));
|
||||||
|
|
||||||
|
// Test JSValue with custom prototype.
|
||||||
|
var num_wrapper = Object(42);
|
||||||
|
num_wrapper.__proto__ = { __proto__: null,
|
||||||
|
toString: function() { return true; } };
|
||||||
|
assertEquals('1', JSON.stringify(num_wrapper));
|
||||||
|
|
||||||
|
var str_wrapper = Object('2');
|
||||||
|
str_wrapper.__proto__ = { __proto__: null,
|
||||||
|
toString: function() { return true; } };
|
||||||
|
assertEquals('"true"', JSON.stringify(str_wrapper));
|
||||||
|
|
||||||
|
var bool_wrapper = Object(false);
|
||||||
|
bool_wrapper.__proto__ = { __proto__: null,
|
||||||
|
toString: function() { return true; } };
|
||||||
|
// Note that toString function is not evaluated here!
|
||||||
|
assertEquals('false', JSON.stringify(bool_wrapper));
|
||||||
|
|
||||||
|
// Test getters.
|
||||||
|
var counter = 0;
|
||||||
|
var getter_obj = { get getter() {
|
||||||
|
counter++;
|
||||||
|
return 123;
|
||||||
|
} };
|
||||||
|
assertEquals('{"getter":123}', JSON.stringify(getter_obj));
|
||||||
|
assertEquals(1, counter);
|
||||||
|
|
||||||
|
// Test toJSON function.
|
||||||
|
var tojson_obj = { toJSON: function() {
|
||||||
|
counter++;
|
||||||
|
return [1, 2];
|
||||||
|
},
|
||||||
|
a: 1};
|
||||||
|
assertEquals('[1,2]', JSON.stringify(tojson_obj));
|
||||||
|
assertEquals(2, counter);
|
||||||
|
|
||||||
|
// Test that we don't recursively look for the toJSON function.
|
||||||
|
var tojson_proto_obj = { a: 'fail' };
|
||||||
|
tojson_proto_obj.__proto__ = { toJSON: function() {
|
||||||
|
counter++;
|
||||||
|
return tojson_obj;
|
||||||
|
} };
|
||||||
|
assertEquals('{"a":1}', JSON.stringify(tojson_proto_obj));
|
||||||
|
|
||||||
|
// Test toJSON produced by a getter.
|
||||||
|
var tojson_via_getter = { get toJSON() {
|
||||||
|
return function(x) {
|
||||||
|
counter++;
|
||||||
|
return 321;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
a: 1 };
|
||||||
|
assertEquals('321', JSON.stringify(tojson_via_getter));
|
||||||
|
|
||||||
|
// Test toJSON with key.
|
||||||
|
tojson_obj = { toJSON: function(key) { return key + key; } };
|
||||||
|
var tojson_with_key_1 = { a: tojson_obj, b: tojson_obj };
|
||||||
|
assertEquals('{"a":"aa","b":"bb"}', JSON.stringify(tojson_with_key_1));
|
||||||
|
var tojson_with_key_2 = [ tojson_obj, tojson_obj ];
|
||||||
|
assertEquals('["00","11"]', JSON.stringify(tojson_with_key_2));
|
||||||
|
|
||||||
|
// Test toJSON with exception.
|
||||||
|
var tojson_ex = { toJSON: function(key) { throw "123" } };
|
||||||
|
assertThrows(function() { JSON.stringify(tojson_ex); });
|
||||||
|
|
||||||
|
// Test holes in arrays.
|
||||||
var fast_smi = [1, 2, 3, 4];
|
var fast_smi = [1, 2, 3, 4];
|
||||||
fast_smi.__proto__ = [7, 7, 7, 7];
|
fast_smi.__proto__ = [7, 7, 7, 7];
|
||||||
delete fast_smi[2];
|
delete fast_smi[2];
|
||||||
|
@ -37,5 +37,5 @@ for (var i = 0; i < 10000; i++) a.push(new_space_string);
|
|||||||
// screw up reading from the correct location.
|
// screw up reading from the correct location.
|
||||||
json1 = JSON.stringify(a);
|
json1 = JSON.stringify(a);
|
||||||
json2 = JSON.stringify(a);
|
json2 = JSON.stringify(a);
|
||||||
assertEquals(json1, json2);
|
assertEquals(json1, json2, "GC caused JSON.stringify to fail.");
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user