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) {
function DateToISOString() {
var t = DATE_VALUE(this);
if (NUMBER_IS_NAN(t)) return kInvalidDate;
return this.getUTCFullYear() + '-' + PadInt(this.getUTCMonth() + 1, 2) +
'-' + PadInt(this.getUTCDate(), 2) + 'T' + PadInt(this.getUTCHours(), 2) +
':' + PadInt(this.getUTCMinutes(), 2) + ':' + PadInt(this.getUTCSeconds(), 2) +
return this.getUTCFullYear() +
'-' + PadInt(this.getUTCMonth() + 1, 2) +
'-' + PadInt(this.getUTCDate(), 2) +
'T' + PadInt(this.getUTCHours(), 2) +
':' + PadInt(this.getUTCMinutes(), 2) +
':' + PadInt(this.getUTCSeconds(), 2) +
'.' + PadInt(this.getUTCMilliseconds(), 3) +
'Z';
}
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) {
function JSONSerialize(key, holder, replacer, stack, indent, gap) {
var value = holder[key];
if (IS_OBJECT(value) && value) {
if (IS_SPEC_OBJECT(value)) {
var toJSON = value.toJSON;
if (IS_FUNCTION(toJSON)) {
value = toJSON.call(value, key);
value = %_CallFunction(value, key, toJSON);
}
}
if (IS_FUNCTION(replacer)) {
value = replacer.call(holder, key, value);
value = %_CallFunction(holder, key, value, replacer);
}
// Unwrap value if necessary
if (IS_OBJECT(value)) {
if (IS_NUMBER_WRAPPER(value)) {
value = $Number(value);
if (IS_STRING(value)) {
return %QuoteJSONString(value);
} else if (IS_NUMBER(value)) {
return $isFinite(value) ? $String(value) : "null";
} else if (IS_BOOLEAN(value)) {
return value ? "true" : "false";
} else if (IS_NULL(value)) {
return "null";
} 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);
} else if (IS_NUMBER_WRAPPER(value)) {
value = ToNumber(value);
return $isFinite(value) ? ToString(value) : "null";
} else if (IS_STRING_WRAPPER(value)) {
value = $String(value);
return %QuoteJSONString(ToString(value));
} else if (IS_BOOLEAN_WRAPPER(value)) {
value = %_ValueOf(value);
return %_ValueOf(value) ? "true" : "false";
} else {
return SerializeObject(value, replacer, stack, indent, gap);
}
}
switch (typeof value) {
case "string":
return %QuoteJSONString(value);
case "object":
if (!value) {
return "null";
} else if (IS_ARRAY(value)) {
return SerializeArray(value, replacer, stack, indent, gap);
} else {
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) {
function BasicJSONSerialize(key, holder, stack, builder) {
var value = holder[key];
if (IS_OBJECT(value) && value) {
if (IS_SPEC_OBJECT(value)) {
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)) {
builder.push(%QuoteJSONString(value));
} else if (IS_NUMBER(value)) {
builder.push(($isFinite(value) ? %_NumberToString(value) : "null"));
} else if (IS_BOOLEAN(value)) {
builder.push((value ? "true" : "false"));
} else if (IS_OBJECT(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 = %_ValueOf(value);
builder.push(($isFinite(value) ? %_NumberToString(value) : "null"));
value = ToNumber(value);
builder.push(($isFinite(value) ? %_NumberToString(value) : "null"));
} else if (IS_STRING_WRAPPER(value)) {
builder.push(%QuoteJSONString(%_ValueOf(value)));
builder.push(%QuoteJSONString(ToString(value)));
} else if (IS_BOOLEAN_WRAPPER(value)) {
builder.push((%_ValueOf(value) ? "true" : "false"));
builder.push(%_ValueOf(value) ? "true" : "false");
} else if (IS_ARRAY(value)) {
BasicSerializeArray(value, stack, builder);
} else {
// Regular non-wrapped object
if (!value) {
builder.push("null");
} else if (IS_ARRAY(value)) {
BasicSerializeArray(value, stack, builder);
} else {
BasicSerializeObject(value, stack, builder);
}
BasicSerializeObject(value, stack, builder);
}
}
}
function JSONStringify(value, replacer, space) {
if (IS_UNDEFINED(replacer) && IS_UNDEFINED(space)) {
if (%_ArgumentsLength() == 1) {
var builder = [];
BasicJSONSerialize('', {'': value}, [], builder);
if (builder.length == 0) return;
......@@ -280,21 +280,18 @@ function JSONStringify(value, replacer, space) {
if (IS_OBJECT(space)) {
// Unwrap 'space' if it is wrapped
if (IS_NUMBER_WRAPPER(space)) {
space = $Number(space);
space = ToNumber(space);
} else if (IS_STRING_WRAPPER(space)) {
space = $String(space);
space = ToString(space);
}
}
var gap;
if (IS_NUMBER(space)) {
space = $Math.min(ToInteger(space), 10);
gap = "";
for (var i = 0; i < space; i++) {
gap += " ";
}
space = MathMax(0, MathMin(ToInteger(space), 10));
gap = SubString(" ", 0, space);
} else if (IS_STRING(space)) {
if (space.length > 10) {
gap = space.substring(0, 10);
gap = SubString(space, 0, 10);
} else {
gap = space;
}
......
......@@ -190,7 +190,6 @@ function FormatMessage(message) {
illegal_return: "Illegal return statement",
error_loading_debugger: "Error loading debugger",
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",
circular_structure: "Converting circular structure to JSON",
obj_ctor_property_non_object: "Object.%0 called on non-object",
......
......@@ -872,11 +872,6 @@ ReplaceResultBuilder.prototype.generate = function() {
}
function StringToJSON(key) {
return CheckJSONPrimitive(this.valueOf());
}
// -------------------------------------------------------------------
function SetupString() {
......@@ -926,8 +921,7 @@ function SetupString() {
"small", StringSmall,
"strike", StringStrike,
"sub", StringSub,
"sup", StringSup,
"toJSON", StringToJSON
"sup", StringSup
));
}
......
......@@ -909,19 +909,13 @@ function BooleanValueOf() {
}
function BooleanToJSON(key) {
return CheckJSONPrimitive(this.valueOf());
}
// ----------------------------------------------------------------------------
function SetupBoolean() {
InstallFunctions($Boolean.prototype, DONT_ENUM, $Array(
"toString", BooleanToString,
"valueOf", BooleanValueOf,
"toJSON", BooleanToJSON
"valueOf", BooleanValueOf
));
}
......@@ -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() {
......@@ -1073,15 +1055,13 @@ function SetupNumber() {
"valueOf", NumberValueOf,
"toFixed", NumberToFixed,
"toExponential", NumberToExponential,
"toPrecision", NumberToPrecision,
"toJSON", NumberToJSON
"toPrecision", NumberToPrecision
));
}
SetupNumber();
// ----------------------------------------------------------------------------
// Function
......
......@@ -25,45 +25,6 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// 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
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());
......@@ -74,9 +35,6 @@ assertEquals("foo", n1.toJSON());
var n2 = new Date(10001);
n2.toISOString = null;
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);
n4.toISOString = function () {
assertEquals(0, arguments.length);
......@@ -88,9 +46,47 @@ assertEquals(null, n4.toJSON());
assertTrue(Object.prototype === JSON.__proto__);
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
for (var p in this)
for (var p in this) {
assertFalse(p == "JSON");
}
// Parse
assertEquals({}, JSON.parse("{}"));
......@@ -286,7 +282,7 @@ assertEquals('["10"]', JSON.stringify(array));
// The gap is capped at ten characters if specified as string.
assertEquals('{\n "a": "b",\n "c": "d"\n}',
JSON.stringify({a:"b",c:"d"}, null,
JSON.stringify({a:"b",c:"d"}, null,
" /*characters after 10th*/"));
//The gap is capped at ten characters if specified as number.
......@@ -301,16 +297,16 @@ assertEquals('{"x":true}', JSON.stringify({x: Boolean}, newx));
assertEquals(undefined, JSON.stringify(undefined));
assertEquals(undefined, JSON.stringify(function () { }));
// Arrays with missing, undefined or function elements have those elements
// Arrays with missing, undefined or function elements have those elements
// replaced by null.
assertEquals("[null,null,null]",
assertEquals("[null,null,null]",
JSON.stringify([undefined,,function(){}]));
// Objects with undefined or function properties (including replaced properties)
// have those properties ignored.
assertEquals('{}',
assertEquals('{}',
JSON.stringify({a: undefined, b: function(){}, c: 42, d: 42},
function(k, v) { if (k == "c") return undefined;
function(k, v) { if (k == "c") return undefined;
if (k == "d") return function(){};
return v; }));
......@@ -334,7 +330,7 @@ for (var i = 0; i < 65536; i++) {
// Step 2.a
expected = '\\' + string;
} else if ("\b\t\n\r\f".indexOf(string) >= 0) {
// Step 2.b
// Step 2.b
if (string == '\b') expected = '\\b';
else if (string == '\t') expected = '\\t';
else if (string == '\n') expected = '\\n';
......@@ -349,6 +345,73 @@ for (var i = 0; i < 65536; i++) {
}
} else {
expected = string;
}
}
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