Commit f118f441 authored by lrn@chromium.org's avatar lrn@chromium.org

Fix regression in JSON serialization of RegExps.

Tweaks to the serialization.

Review URL: http://codereview.chromium.org/5676005

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@6018 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent d25755ee
...@@ -984,16 +984,24 @@ function PadInt(n, digits) { ...@@ -984,16 +984,24 @@ function PadInt(n, digits) {
function DateToISOString() { function DateToISOString() {
var t = DATE_VALUE(this); var t = DATE_VALUE(this);
if (NUMBER_IS_NAN(t)) return kInvalidDate; if (NUMBER_IS_NAN(t)) return kInvalidDate;
return this.getUTCFullYear() + '-' + PadInt(this.getUTCMonth() + 1, 2) + return this.getUTCFullYear() +
'-' + PadInt(this.getUTCDate(), 2) + 'T' + PadInt(this.getUTCHours(), 2) + '-' + PadInt(this.getUTCMonth() + 1, 2) +
':' + PadInt(this.getUTCMinutes(), 2) + ':' + PadInt(this.getUTCSeconds(), 2) + '-' + PadInt(this.getUTCDate(), 2) +
'T' + PadInt(this.getUTCHours(), 2) +
':' + PadInt(this.getUTCMinutes(), 2) +
':' + PadInt(this.getUTCSeconds(), 2) +
'.' + PadInt(this.getUTCMilliseconds(), 3) + '.' + PadInt(this.getUTCMilliseconds(), 3) +
'Z'; 'Z';
} }
function DateToJSON(key) { function DateToJSON(key) {
return CheckJSONPrimitive(this.toISOString()); var o = ToObject(this);
var tv = DefaultNumber(o);
if (IS_NUMBER(tv) && !$isFinite(tv)) {
return null;
}
return o.toISOString();
} }
......
...@@ -146,41 +146,40 @@ function SerializeObject(value, replacer, stack, indent, gap) { ...@@ -146,41 +146,40 @@ function SerializeObject(value, replacer, stack, indent, gap) {
function JSONSerialize(key, holder, replacer, stack, indent, gap) { function JSONSerialize(key, holder, replacer, stack, indent, gap) {
var value = holder[key]; var value = holder[key];
if (IS_OBJECT(value) && value) { if (IS_SPEC_OBJECT(value)) {
var toJSON = value.toJSON; var toJSON = value.toJSON;
if (IS_FUNCTION(toJSON)) { if (IS_FUNCTION(toJSON)) {
value = toJSON.call(value, key); value = %_CallFunction(value, key, toJSON);
} }
} }
if (IS_FUNCTION(replacer)) { if (IS_FUNCTION(replacer)) {
value = replacer.call(holder, key, value); value = %_CallFunction(holder, key, value, replacer);
} }
// Unwrap value if necessary if (IS_STRING(value)) {
if (IS_OBJECT(value)) {
if (IS_NUMBER_WRAPPER(value)) {
value = $Number(value);
} else if (IS_STRING_WRAPPER(value)) {
value = $String(value);
} else if (IS_BOOLEAN_WRAPPER(value)) {
value = %_ValueOf(value);
}
}
switch (typeof value) {
case "string":
return %QuoteJSONString(value); return %QuoteJSONString(value);
case "object": } else if (IS_NUMBER(value)) {
if (!value) { return $isFinite(value) ? $String(value) : "null";
} else if (IS_BOOLEAN(value)) {
return value ? "true" : "false";
} else if (IS_NULL(value)) {
return "null"; return "null";
} else if (IS_ARRAY(value)) { } else if (IS_SPEC_OBJECT(value) && !(typeof value == "function")) {
// Non-callable object. If it's a primitive wrapper, it must be unwrapped.
if (IS_ARRAY(value)) {
return SerializeArray(value, replacer, stack, indent, gap); return SerializeArray(value, replacer, stack, indent, gap);
} else if (IS_NUMBER_WRAPPER(value)) {
value = ToNumber(value);
return $isFinite(value) ? ToString(value) : "null";
} else if (IS_STRING_WRAPPER(value)) {
return %QuoteJSONString(ToString(value));
} else if (IS_BOOLEAN_WRAPPER(value)) {
return %_ValueOf(value) ? "true" : "false";
} else { } else {
return SerializeObject(value, replacer, stack, indent, gap); return SerializeObject(value, replacer, stack, indent, gap);
} }
case "number":
return $isFinite(value) ? $String(value) : "null";
case "boolean":
return value ? "true" : "false";
} }
// Undefined or a callable object.
return void 0;
} }
...@@ -236,40 +235,41 @@ function BasicSerializeObject(value, stack, builder) { ...@@ -236,40 +235,41 @@ function BasicSerializeObject(value, stack, builder) {
function BasicJSONSerialize(key, holder, stack, builder) { function BasicJSONSerialize(key, holder, stack, builder) {
var value = holder[key]; var value = holder[key];
if (IS_OBJECT(value) && value) { if (IS_SPEC_OBJECT(value)) {
var toJSON = value.toJSON; var toJSON = value.toJSON;
if (IS_FUNCTION(toJSON)) value = toJSON.call(value, $String(key)); if (IS_FUNCTION(toJSON)) {
value = %_CallFunction(value, ToString(key), toJSON);
}
} }
if (IS_STRING(value)) { if (IS_STRING(value)) {
builder.push(%QuoteJSONString(value)); builder.push(%QuoteJSONString(value));
} else if (IS_NUMBER(value)) { } else if (IS_NUMBER(value)) {
builder.push(($isFinite(value) ? %_NumberToString(value) : "null")); builder.push(($isFinite(value) ? %_NumberToString(value) : "null"));
} else if (IS_BOOLEAN(value)) { } else if (IS_BOOLEAN(value)) {
builder.push((value ? "true" : "false")); builder.push(value ? "true" : "false");
} else if (IS_OBJECT(value)) { } 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 // Unwrap value if necessary
if (IS_NUMBER_WRAPPER(value)) { if (IS_NUMBER_WRAPPER(value)) {
value = %_ValueOf(value); value = ToNumber(value);
builder.push(($isFinite(value) ? %_NumberToString(value) : "null")); builder.push(($isFinite(value) ? %_NumberToString(value) : "null"));
} else if (IS_STRING_WRAPPER(value)) { } else if (IS_STRING_WRAPPER(value)) {
builder.push(%QuoteJSONString(%_ValueOf(value))); builder.push(%QuoteJSONString(ToString(value)));
} else if (IS_BOOLEAN_WRAPPER(value)) { } else if (IS_BOOLEAN_WRAPPER(value)) {
builder.push((%_ValueOf(value) ? "true" : "false")); builder.push(%_ValueOf(value) ? "true" : "false");
} else {
// Regular non-wrapped object
if (!value) {
builder.push("null");
} else if (IS_ARRAY(value)) { } else if (IS_ARRAY(value)) {
BasicSerializeArray(value, stack, builder); BasicSerializeArray(value, stack, builder);
} else { } else {
BasicSerializeObject(value, stack, builder); BasicSerializeObject(value, stack, builder);
} }
} }
}
} }
function JSONStringify(value, replacer, space) { function JSONStringify(value, replacer, space) {
if (IS_UNDEFINED(replacer) && IS_UNDEFINED(space)) { if (%_ArgumentsLength() == 1) {
var builder = []; var builder = [];
BasicJSONSerialize('', {'': value}, [], builder); BasicJSONSerialize('', {'': value}, [], builder);
if (builder.length == 0) return; if (builder.length == 0) return;
...@@ -280,21 +280,18 @@ function JSONStringify(value, replacer, space) { ...@@ -280,21 +280,18 @@ function JSONStringify(value, replacer, space) {
if (IS_OBJECT(space)) { if (IS_OBJECT(space)) {
// Unwrap 'space' if it is wrapped // Unwrap 'space' if it is wrapped
if (IS_NUMBER_WRAPPER(space)) { if (IS_NUMBER_WRAPPER(space)) {
space = $Number(space); space = ToNumber(space);
} else if (IS_STRING_WRAPPER(space)) { } else if (IS_STRING_WRAPPER(space)) {
space = $String(space); space = ToString(space);
} }
} }
var gap; var gap;
if (IS_NUMBER(space)) { if (IS_NUMBER(space)) {
space = $Math.min(ToInteger(space), 10); space = MathMax(0, MathMin(ToInteger(space), 10));
gap = ""; gap = SubString(" ", 0, space);
for (var i = 0; i < space; i++) {
gap += " ";
}
} else if (IS_STRING(space)) { } else if (IS_STRING(space)) {
if (space.length > 10) { if (space.length > 10) {
gap = space.substring(0, 10); gap = SubString(space, 0, 10);
} else { } else {
gap = space; gap = space;
} }
......
...@@ -190,7 +190,6 @@ function FormatMessage(message) { ...@@ -190,7 +190,6 @@ function FormatMessage(message) {
illegal_return: "Illegal return statement", illegal_return: "Illegal return statement",
error_loading_debugger: "Error loading debugger", error_loading_debugger: "Error loading debugger",
no_input_to_regexp: "No input to %0", no_input_to_regexp: "No input to %0",
result_not_primitive: "Result of %0 must be a primitive, was %1",
invalid_json: "String '%0' is not valid JSON", invalid_json: "String '%0' is not valid JSON",
circular_structure: "Converting circular structure to JSON", circular_structure: "Converting circular structure to JSON",
obj_ctor_property_non_object: "Object.%0 called on non-object", obj_ctor_property_non_object: "Object.%0 called on non-object",
......
...@@ -872,11 +872,6 @@ ReplaceResultBuilder.prototype.generate = function() { ...@@ -872,11 +872,6 @@ ReplaceResultBuilder.prototype.generate = function() {
} }
function StringToJSON(key) {
return CheckJSONPrimitive(this.valueOf());
}
// ------------------------------------------------------------------- // -------------------------------------------------------------------
function SetupString() { function SetupString() {
...@@ -926,8 +921,7 @@ function SetupString() { ...@@ -926,8 +921,7 @@ function SetupString() {
"small", StringSmall, "small", StringSmall,
"strike", StringStrike, "strike", StringStrike,
"sub", StringSub, "sub", StringSub,
"sup", StringSup, "sup", StringSup
"toJSON", StringToJSON
)); ));
} }
......
...@@ -909,19 +909,13 @@ function BooleanValueOf() { ...@@ -909,19 +909,13 @@ function BooleanValueOf() {
} }
function BooleanToJSON(key) {
return CheckJSONPrimitive(this.valueOf());
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
function SetupBoolean() { function SetupBoolean() {
InstallFunctions($Boolean.prototype, DONT_ENUM, $Array( InstallFunctions($Boolean.prototype, DONT_ENUM, $Array(
"toString", BooleanToString, "toString", BooleanToString,
"valueOf", BooleanValueOf, "valueOf", BooleanValueOf
"toJSON", BooleanToJSON
)); ));
} }
...@@ -1021,18 +1015,6 @@ function NumberToPrecision(precision) { ...@@ -1021,18 +1015,6 @@ function NumberToPrecision(precision) {
} }
function CheckJSONPrimitive(val) {
if (!IsPrimitive(val))
throw MakeTypeError('result_not_primitive', ['toJSON', val]);
return val;
}
function NumberToJSON(key) {
return CheckJSONPrimitive(this.valueOf());
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
function SetupNumber() { function SetupNumber() {
...@@ -1073,15 +1055,13 @@ function SetupNumber() { ...@@ -1073,15 +1055,13 @@ function SetupNumber() {
"valueOf", NumberValueOf, "valueOf", NumberValueOf,
"toFixed", NumberToFixed, "toFixed", NumberToFixed,
"toExponential", NumberToExponential, "toExponential", NumberToExponential,
"toPrecision", NumberToPrecision, "toPrecision", NumberToPrecision
"toJSON", NumberToJSON
)); ));
} }
SetupNumber(); SetupNumber();
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Function // Function
......
...@@ -25,45 +25,6 @@ ...@@ -25,45 +25,6 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
function GenericToJSONChecks(Constructor, value, alternative) {
var n1 = new Constructor(value);
n1.valueOf = function () { return alternative; };
assertEquals(alternative, n1.toJSON());
var n2 = new Constructor(value);
n2.valueOf = null;
assertThrows(function () { n2.toJSON(); }, TypeError);
var n3 = new Constructor(value);
n3.valueOf = function () { return {}; };
assertThrows(function () { n3.toJSON(); }, TypeError, 'result_not_primitive');
var n4 = new Constructor(value);
n4.valueOf = function () {
assertEquals(0, arguments.length);
assertEquals(this, n4);
return null;
};
assertEquals(null, n4.toJSON());
}
// Number toJSON
assertEquals(3, (3).toJSON());
assertEquals(3, (3).toJSON(true));
assertEquals(4, (new Number(4)).toJSON());
GenericToJSONChecks(Number, 5, 6);
// Boolean toJSON
assertEquals(true, (true).toJSON());
assertEquals(true, (true).toJSON(false));
assertEquals(false, (false).toJSON());
assertEquals(true, (new Boolean(true)).toJSON());
GenericToJSONChecks(Boolean, true, false);
GenericToJSONChecks(Boolean, false, true);
// String toJSON
assertEquals("flot", "flot".toJSON());
assertEquals("flot", "flot".toJSON(3));
assertEquals("tolf", (new String("tolf")).toJSON());
GenericToJSONChecks(String, "x", "y");
// Date toJSON // Date toJSON
assertEquals("1970-01-01T00:00:00.000Z", new Date(0).toJSON()); assertEquals("1970-01-01T00:00:00.000Z", new Date(0).toJSON());
assertEquals("1979-01-11T08:00:00.000Z", new Date("1979-01-11 08:00 GMT").toJSON()); assertEquals("1979-01-11T08:00:00.000Z", new Date("1979-01-11 08:00 GMT").toJSON());
...@@ -74,9 +35,6 @@ assertEquals("foo", n1.toJSON()); ...@@ -74,9 +35,6 @@ assertEquals("foo", n1.toJSON());
var n2 = new Date(10001); var n2 = new Date(10001);
n2.toISOString = null; n2.toISOString = null;
assertThrows(function () { n2.toJSON(); }, TypeError); assertThrows(function () { n2.toJSON(); }, TypeError);
var n3 = new Date(10002);
n3.toISOString = function () { return {}; };
assertThrows(function () { n3.toJSON(); }, TypeError, "result_not_primitive");
var n4 = new Date(10003); var n4 = new Date(10003);
n4.toISOString = function () { n4.toISOString = function () {
assertEquals(0, arguments.length); assertEquals(0, arguments.length);
...@@ -88,9 +46,47 @@ assertEquals(null, n4.toJSON()); ...@@ -88,9 +46,47 @@ assertEquals(null, n4.toJSON());
assertTrue(Object.prototype === JSON.__proto__); assertTrue(Object.prototype === JSON.__proto__);
assertEquals("[object JSON]", Object.prototype.toString.call(JSON)); assertEquals("[object JSON]", Object.prototype.toString.call(JSON));
//Test Date.prototype.toJSON as generic function.
var d1 = {toJSON: Date.prototype.toJSON,
toISOString: function() { return 42; }};
assertEquals(42, d1.toJSON());
var d2 = {toJSON: Date.prototype.toJSON,
valueOf: function() { return Infinity; },
toISOString: function() { return 42; }};
assertEquals(null, d2.toJSON());
var d3 = {toJSON: Date.prototype.toJSON,
valueOf: "not callable",
toString: function() { return Infinity; },
toISOString: function() { return 42; }};
assertEquals(null, d3.toJSON());
var d4 = {toJSON: Date.prototype.toJSON,
valueOf: "not callable",
toString: "not callable either",
toISOString: function() { return 42; }};
assertThrows("d4.toJSON()", TypeError); // ToPrimitive throws.
var d5 = {toJSON: Date.prototype.toJSON,
valueOf: "not callable",
toString: function() { return "Infinity"; },
toISOString: function() { return 42; }};
assertEquals(42, d5.toJSON());
var d6 = {toJSON: Date.prototype.toJSON,
toISOString: function() { return ["not primitive"]; }};
assertEquals(["not primitive"], d6.toJSON());
var d7 = {toJSON: Date.prototype.toJSON,
ISOString: "not callable"};
assertThrows("d7.toJSON()", TypeError);
// DontEnum // DontEnum
for (var p in this) for (var p in this) {
assertFalse(p == "JSON"); assertFalse(p == "JSON");
}
// Parse // Parse
assertEquals({}, JSON.parse("{}")); assertEquals({}, JSON.parse("{}"));
...@@ -352,3 +348,70 @@ for (var i = 0; i < 65536; i++) { ...@@ -352,3 +348,70 @@ for (var i = 0; i < 65536; i++) {
} }
assertEquals('"' + expected + '"', encoded, "Codepoint " + i); assertEquals('"' + expected + '"', encoded, "Codepoint " + i);
} }
// Ensure that wrappers and callables are handled correctly.
var num37 = new Number(42);
num37.valueOf = function() { return 37; };
var numFoo = new Number(42);
numFoo.valueOf = "not callable";
numFoo.toString = function() { return "foo"; };
var numTrue = new Number(42);
numTrue.valueOf = function() { return true; }
var strFoo = new String("bar");
strFoo.toString = function() { return "foo"; };
var str37 = new String("bar");
str37.toString = "not callable";
str37.valueOf = function() { return 37; };
var strTrue = new String("bar");
strTrue.toString = function() { return true; }
var func = function() { /* Is callable */ };
var funcJSON = function() { /* Is callable */ };
funcJSON.toJSON = function() { return "has toJSON"; };
var re = /Is callable/;
var reJSON = /Is callable/;
reJSON.toJSON = function() { return "has toJSON"; };
assertEquals(
'[37,null,1,"foo","37","true",null,"has toJSON",null,"has toJSON"]',
JSON.stringify([num37, numFoo, numTrue,
strFoo, str37, strTrue,
func, funcJSON, re, reJSON]));
var oddball = Object(42);
oddball.__proto__ = { __proto__: null, toString: function() { return true; } };
assertEquals('1', JSON.stringify(oddball));
var getCount = 0;
var callCount = 0;
var counter = { get toJSON() { getCount++;
return function() { callCount++;
return 42; }; } };
assertEquals('42', JSON.stringify(counter));
assertEquals(1, getCount);
assertEquals(1, callCount);
var oddball2 = Object(42);
var oddball3 = Object("foo");
oddball3.__proto__ = { __proto__: null,
toString: "not callable",
valueOf: function() { return true; } };
oddball2.__proto__ = { __proto__: null,
toJSON: function () { return oddball3; } }
assertEquals('"true"', JSON.stringify(oddball2));
var falseNum = Object("37");
falseNum.__proto__ = Number.prototype;
falseNum.toString = function() { return 42; };
assertEquals('"42"', JSON.stringify(falseNum));
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