v8/test/webkit/fast/js/JSON-parse-reviver.js
neis a5380fe9ed JSON.parse: properly deal with reviver result
When the reviver returns undefined, the property in question must be deleted
even for arrays.  So far this only happened for non-array objects.

Also change the property enumeration to be spec-conformant, which is observable when the reviver modifies its "this" object directly.  There are a few further issues that need to be addressed in a separate CL.

R=yangguo@chromium.org
BUG=

Review URL: https://codereview.chromium.org/1506933003

Cr-Commit-Position: refs/heads/master@{#32750}
2015-12-10 12:49:06 +00:00

205 lines
8.2 KiB
JavaScript

// Copyright 2013 the V8 project authors. All rights reserved.
// Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. 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.
//
// THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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.
description("Test behaviour of JSON reviver function.")
if (!Array.isArray)
Array.isArray = function(o) { return o.constructor === Array; }
function arrayReviver(i,v) {
if (i != "") {
currentHolder = this;
debug("");
debug("Ensure the holder for our array is indeed an array");
shouldBeTrue("Array.isArray(currentHolder)");
shouldBe("currentHolder.length", "" + expectedLength);
if (i > 0) {
debug("");
debug("Ensure that we always get the same holder");
shouldBe("currentHolder", "lastHolder");
}
switch (Number(i)) {
case 0:
v = undefined;
debug("");
debug("Ensure that the holder already has all the properties present at the start of filtering");
shouldBe("currentHolder[0]", '"a value"');
shouldBe("currentHolder[1]", '"another value"');
shouldBe("currentHolder[2]", '"and another value"');
shouldBe("currentHolder[3]", '"to delete"');
shouldBe("currentHolder[4]", '"extra value"');
break;
case 1:
debug("");
debug("Ensure that returning undefined has removed the property 0 from the holder during filtering.");
shouldBeFalse("currentHolder.hasOwnProperty(0)");
currentHolder[2] = "a replaced value";
break;
case 2:
debug("");
debug("Ensure that changing the value of a property is reflected while filtering.")
shouldBe("currentHolder[2]", '"a replaced value"');
value = v;
debug("");
debug("Ensure that the changed value is reflected in the arguments passed to the reviver");
shouldBe("value", "currentHolder[2]");
delete this[3];
break;
case 3:
debug("");
debug("Ensure that we visited a value that we have deleted, and that deletion is reflected while filtering.");
shouldBeFalse("currentHolder.hasOwnProperty(3)");
value = v;
debug("");
debug("Ensure that when visiting a deleted property value is undefined");
shouldBeUndefined("value");
this.length = 3;
v = "undelete the property";
expectedLength = 4;
break;
case 4:
if (this.length != 4) {
testFailed("Did not call reviver for deleted property");
expectedLength = this.length = 3;
break;
}
case 5:
testPassed("Ensured that property was visited despite Array length being reduced.");
value = v;
shouldBeUndefined("value");
this[10] = "fail";
break;
default:
testFailed("Visited unexpected property " + i + " with value " + v);
}
}
lastHolder = this;
return v;
}
expectedLength = 5;
var result = JSON.parse('["a value", "another value", "and another value", "to delete", "extra value"]', arrayReviver);
debug("");
debug("Ensure that we created the root holder as specified in ES5");
shouldBeTrue("'' in lastHolder");
shouldBe("result", "lastHolder['']");
debug("");
debug("Ensure that a deleted value is revived if the reviver function returns a value");
shouldBeTrue("result.hasOwnProperty(3)");
function objectReviver(i,v) {
if (i != "") {
currentHolder = this;
shouldBeTrue("currentHolder != globalObject");
if (seen) {
debug("");
debug("Ensure that we get the same holder object for each property");
shouldBe("currentHolder", "lastHolder");
}
seen = true;
switch (i) {
case "a property":
v = undefined;
debug("");
debug("Ensure that the holder already has all the properties present at the start of filtering");
shouldBe("currentHolder['a property']", '"a value"');
shouldBe("currentHolder['another property']", '"another value"');
shouldBe("currentHolder['and another property']", '"and another value"');
shouldBe("currentHolder['to delete']", '"will be deleted"');
break;
case "another property":
debug("");
debug("Ensure that returning undefined has correctly removed the property 'a property' from the holder object");
shouldBeFalse("currentHolder.hasOwnProperty('a property')");
currentHolder['and another property'] = "a replaced value";
break;
case "and another property":
debug("Ensure that changing the value of a property is reflected while filtering.");
shouldBe("currentHolder['and another property']", '"a replaced value"');
value = v;
debug("");
debug("Ensure that the changed value is reflected in the arguments passed to the reviver");
shouldBe("value", '"a replaced value"');
delete this["to delete"];
break;
case "to delete":
debug("");
debug("Ensure that we visited a value that we have deleted, and that deletion is reflected while filtering.");
shouldBeFalse("currentHolder.hasOwnProperty('to delete')");
value = v;
debug("");
debug("Ensure that when visiting a deleted property value is undefined");
shouldBeUndefined("value");
v = "undelete the property";
this["new property"] = "fail";
break;
default:
testFailed("Visited unexpected property " + i + " with value " + v);
}
}
lastHolder = this;
return v;
}
debug("");
debug("Test behaviour of revivor used in conjunction with an object");
var seen = false;
var globalObject = this;
var result = JSON.parse('{"a property" : "a value", "another property" : "another value", "and another property" : "and another value", "to delete" : "will be deleted"}', objectReviver);
debug("");
debug("Ensure that we created the root holder as specified in ES5");
shouldBeTrue("lastHolder.hasOwnProperty('')");
shouldBeFalse("result.hasOwnProperty('a property')");
shouldBeTrue("result.hasOwnProperty('to delete')");
shouldBe("result", "lastHolder['']");
debug("");
debug("Test behaviour of revivor that introduces a cycle");
function reviveAddsCycle(i, v) {
if (i == 0)
this[1] = this;
return v;
}
shouldThrow('JSON.parse("[0,1]", reviveAddsCycle)');
debug("");
debug("Test behaviour of revivor that introduces a new array classed object (the result of a regex)");
var createdBadness = false;
function reviveIntroducesNewArrayLikeObject(i, v) {
if (i == 0 && !createdBadness) {
this[1] = /(a)+/.exec("a");
createdBadness = true;
}
return v;
}
shouldBe('JSON.stringify(JSON.parse("[0,1]", reviveIntroducesNewArrayLikeObject))', '\'[0,["a","a"]]\'');