1// Copyright 2013 the V8 project authors. All rights reserved.
2// Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions
6// are met:
7// 1.  Redistributions of source code must retain the above copyright
8//     notice, this list of conditions and the following disclaimer.
9// 2.  Redistributions in binary form must reproduce the above copyright
10//     notice, this list of conditions and the following disclaimer in the
11//     documentation and/or other materials provided with the distribution.
12//
13// THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16// DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
24description("Test behaviour of JSON reviver function.")
25if (!Array.isArray)
26    Array.isArray = function(o) { return o.constructor === Array; }
27
28function arrayReviver(i,v) {
29    if (i != "") {
30        currentHolder = this;
31        debug("");
32        debug("Ensure the holder for our array is indeed an array");
33        shouldBeTrue("Array.isArray(currentHolder)");
34        shouldBe("currentHolder.length", "" + expectedLength);
35        if (i > 0) {
36            debug("");
37            debug("Ensure that we always get the same holder");
38            shouldBe("currentHolder", "lastHolder");
39        }
40        switch (Number(i)) {
41        case 0:
42            v = undefined;
43            debug("");
44            debug("Ensure that the holder already has all the properties present at the start of filtering");
45            shouldBe("currentHolder[0]", '"a value"');
46            shouldBe("currentHolder[1]", '"another value"');
47            shouldBe("currentHolder[2]", '"and another value"');
48            shouldBe("currentHolder[3]", '"to delete"');
49            shouldBe("currentHolder[4]", '"extra value"');
50            break;
51
52        case 1:
53            debug("");
54            debug("Ensure that returning undefined has removed the property 0 from the holder during filtering.");
55            shouldBeFalse("currentHolder.hasOwnProperty(0)");
56            currentHolder[2] = "a replaced value";
57            break;
58
59        case 2:
60            debug("");
61            debug("Ensure that changing the value of a property is reflected while filtering.")
62            shouldBe("currentHolder[2]", '"a replaced value"');
63            value = v;
64            debug("");
65            debug("Ensure that the changed value is reflected in the arguments passed to the reviver");
66            shouldBe("value", "currentHolder[2]");
67            delete this[3];
68            break;
69
70        case 3:
71            debug("");
72            debug("Ensure that we visited a value that we have deleted, and that deletion is reflected while filtering.");
73            shouldBeFalse("currentHolder.hasOwnProperty(3)");
74            value = v;
75            debug("");
76            debug("Ensure that when visiting a deleted property value is undefined");
77            shouldBeUndefined("value");
78            v = "undelete the property";
79            expectedLength = this.length = 3;
80            break;
81
82        case 4:
83            if (this.length != 3) {
84                testFailed("Did not call reviver for deleted property");
85                expectedLength = this.length = 3;
86                break;
87            }
88
89        case 5:
90            testPassed("Ensured that property was visited despite Array length being reduced.");
91            value = v;
92            shouldBeUndefined("value");
93            this[10] = "fail";
94            break;
95
96        default:
97            testFailed("Visited unexpected property " + i + " with value " + v);
98        }
99    }
100    lastHolder = this;
101    return v;
102}
103expectedLength = 5;
104var result = JSON.parse('["a value", "another value", "and another value", "to delete", "extra value"]', arrayReviver);
105debug("");
106debug("Ensure that we created the root holder as specified in ES5");
107shouldBeTrue("'' in lastHolder");
108shouldBe("result", "lastHolder['']");
109debug("");
110debug("Ensure that a deleted value is revived if the reviver function returns a value");
111shouldBeTrue("result.hasOwnProperty(3)");
112
113function objectReviver(i,v) {
114    if (i != "") {
115        currentHolder = this;
116        shouldBeTrue("currentHolder != globalObject");
117        if (seen) {
118            debug("");
119            debug("Ensure that we get the same holder object for each property");
120            shouldBe("currentHolder", "lastHolder");
121        }
122        seen = true;
123        switch (i) {
124        case "a property":
125            v = undefined;
126            debug("");
127            debug("Ensure that the holder already has all the properties present at the start of filtering");
128            shouldBe("currentHolder['a property']", '"a value"');
129            shouldBe("currentHolder['another property']", '"another value"');
130            shouldBe("currentHolder['and another property']", '"and another value"');
131            shouldBe("currentHolder['to delete']", '"will be deleted"');
132            break;
133
134        case "another property":
135            debug("");
136            debug("Ensure that returning undefined has correctly removed the property 'a property' from the holder object");
137            shouldBeFalse("currentHolder.hasOwnProperty('a property')");
138            currentHolder['and another property'] = "a replaced value";
139            break;
140
141        case "and another property":
142            debug("Ensure that changing the value of a property is reflected while filtering.");
143            shouldBe("currentHolder['and another property']", '"a replaced value"');
144            value = v;
145            debug("");
146            debug("Ensure that the changed value is reflected in the arguments passed to the reviver");
147            shouldBe("value", '"a replaced value"');
148            delete this["to delete"];
149            break;
150
151        case "to delete":
152            debug("");
153            debug("Ensure that we visited a value that we have deleted, and that deletion is reflected while filtering.");
154            shouldBeFalse("currentHolder.hasOwnProperty('to delete')");
155            value = v;
156            debug("");
157            debug("Ensure that when visiting a deleted property value is undefined");
158            shouldBeUndefined("value");
159            v = "undelete the property";
160            this["new property"] = "fail";
161            break;
162        default:
163            testFailed("Visited unexpected property " + i + " with value " + v);
164        }
165    }
166    lastHolder = this;
167    return v;
168}
169
170debug("");
171debug("Test behaviour of revivor used in conjunction with an object");
172var seen = false;
173var globalObject = this;
174var result = JSON.parse('{"a property" : "a value", "another property" : "another value", "and another property" : "and another value", "to delete" : "will be deleted"}', objectReviver);
175debug("");
176debug("Ensure that we created the root holder as specified in ES5");
177shouldBeTrue("lastHolder.hasOwnProperty('')");
178shouldBeFalse("result.hasOwnProperty('a property')");
179shouldBeTrue("result.hasOwnProperty('to delete')");
180shouldBe("result", "lastHolder['']");
181
182debug("");
183debug("Test behaviour of revivor that introduces a cycle");
184function reviveAddsCycle(i, v) {
185    if (i == 0)
186        this[1] = this;
187    return v;
188}
189
190shouldThrow('JSON.parse("[0,1]", reviveAddsCycle)');
191
192debug("");
193debug("Test behaviour of revivor that introduces a new array classed object (the result of a regex)");
194var createdBadness = false;
195function reviveIntroducesNewArrayLikeObject(i, v) {
196    if (i == 0 && !createdBadness) {
197        this[1] = /(a)+/.exec("a");
198        createdBadness = true;
199    }
200    return v;
201}
202
203shouldBe('JSON.stringify(JSON.parse("[0,1]", reviveIntroducesNewArrayLikeObject))', '\'[0,["a","a"]]\'');
204