Commit e8d91b42 authored by yangguo@chromium.org's avatar yangguo@chromium.org

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
parent 64793b3f
This diff is collapsed.
......@@ -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) {
if (%_ArgumentsLength() == 1) {
var result = %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, "");
return %BasicJSONStringify(value);
}
if (IS_OBJECT(space)) {
// Unwrap 'space' if it is wrapped
......@@ -340,6 +206,7 @@ function JSONStringify(value, replacer, space) {
return JSONSerialize('', {'': value}, replacer, new InternalArray(), "", gap);
}
function SetUpJSON() {
%CheckIsBootstrapping();
InstallFunctions($JSON, DONT_ENUM, $Array(
......
......@@ -27,6 +27,89 @@
// 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];
fast_smi.__proto__ = [7, 7, 7, 7];
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.
json1 = JSON.stringify(a);
json2 = JSON.stringify(a);
assertEquals(json1, json2);
assertEquals(json1, json2, "GC caused JSON.stringify to fail.");
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment