1// Copyright 2014 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
24
25function createTests() {
26    var simpleArray = ['a', 'b', 'c'];
27    var simpleObject = {a:"1", b:"2", c:"3"};
28    var complexArray = ['a', 'b', 'c',,,simpleObject, simpleArray, [simpleObject,simpleArray]];
29    var complexObject = {a:"1", b:"2", c:"3", d:undefined, e:null, "":12, get f(){ return simpleArray; }, array: complexArray};
30    var simpleArrayWithProto = ['d', 'e', 'f'];
31    simpleArrayWithProto.__proto__ = simpleObject;
32    var simpleObjectWithProto = {d:"4", e:"5", f:"6", __proto__:simpleObject};
33    var complexArrayWithProto = ['d', 'e', 'f',,,simpleObjectWithProto, simpleArrayWithProto, [simpleObjectWithProto,simpleArrayWithProto]];
34    complexArrayWithProto.__proto__ = simpleObjectWithProto;
35    var complexObjectWithProto = {d:"4", e:"5", f:"6", g:undefined, h:null, "":12, get i(){ return simpleArrayWithProto; }, array2: complexArrayWithProto, __proto__:complexObject};
36    var objectWithSideEffectGetter = {get b() {this.foo=1;}};
37    var objectWithSideEffectGetterAndProto = {__proto__:{foo:"bar"}, get b() {this.foo=1;}};
38    var arrayWithSideEffectGetter = [];
39    arrayWithSideEffectGetter.__defineGetter__("b", function(){this.foo=1;});
40    var arrayWithSideEffectGetterAndProto = [];
41    arrayWithSideEffectGetterAndProto.__defineGetter__("b", function(){this.foo=1;});
42    arrayWithSideEffectGetterAndProto.__proto__ = {foo:"bar"};
43    var result = [];
44    result.push(function(jsonObject){
45        return jsonObject.stringify(1);
46    });
47    result.push(function(jsonObject){
48        return jsonObject.stringify(1.5);
49    });
50    result.push(function(jsonObject){
51        return jsonObject.stringify(-1);
52    });
53    result.push(function(jsonObject){
54        return jsonObject.stringify(-1.5);
55    });
56    result.push(function(jsonObject){
57        return jsonObject.stringify(null);
58    });
59    result.push(function(jsonObject){
60        return jsonObject.stringify("string");
61    });
62    result.push(function(jsonObject){
63        return jsonObject.stringify(new Number(0));
64    });
65    result.push(function(jsonObject){
66        return jsonObject.stringify(new Number(1));
67    });
68    result.push(function(jsonObject){
69        return jsonObject.stringify(new Number(1.5));
70    });
71    result.push(function(jsonObject){
72        return jsonObject.stringify(new Number(-1));
73    });
74    result.push(function(jsonObject){
75        return jsonObject.stringify(new Number(-1.5));
76    });
77    result.push(function(jsonObject){
78        return jsonObject.stringify(new String("a string object"));
79    });
80    result.push(function(jsonObject){
81        return jsonObject.stringify(new Boolean(true));
82    });
83    result.push(function(jsonObject){
84        var value = new Number(1);
85        value.valueOf = function() { return 2; }
86        return jsonObject.stringify(value);
87    });
88    result[result.length - 1].expected = '2';
89    result.push(function(jsonObject){
90        var value = new Boolean(true);
91        value.valueOf = function() { return 2; }
92        return jsonObject.stringify(value);
93    });
94    result[result.length - 1].expected = '2';
95    result.push(function(jsonObject){
96        var value = new String("fail");
97        value.toString = function() { return "converted string"; }
98        return jsonObject.stringify(value);
99    });
100    result[result.length - 1].expected = '"converted string"';
101    result.push(function(jsonObject){
102        return jsonObject.stringify(true);
103    });
104    result.push(function(jsonObject){
105        return jsonObject.stringify(false);
106    });
107    result.push(function(jsonObject){
108        return jsonObject.stringify(new Date(0));
109    });
110    result.push(function(jsonObject){
111        return jsonObject.stringify({toJSON: Date.prototype.toJSON});
112    });
113    result[result.length - 1].throws = true;
114    result.push(function(jsonObject){
115        return jsonObject.stringify({toJSON: Date.prototype.toJSON, toISOString: function(){ return "custom toISOString"; }});
116    });
117    result.push(function(jsonObject){
118        return jsonObject.stringify({toJSON: Date.prototype.toJSON, toISOString: function(){ return {}; }});
119    });
120    result[result.length - 1].throws = true;
121    result.push(function(jsonObject){
122        return jsonObject.stringify({toJSON: Date.prototype.toJSON, toISOString: function(){ throw "An exception"; }});
123    });
124    result[result.length - 1].throws = true;
125    result.push(function(jsonObject){
126        var d = new Date(0);
127        d.toISOString = null;
128        return jsonObject.stringify(d);
129    });
130    result[result.length - 1].throws = true;
131    result.push(function(jsonObject){
132        var d = new Date(0);
133        d.toJSON = undefined;
134        return jsonObject.stringify(d);
135    });
136    result.push(function(jsonObject){
137        return jsonObject.stringify({get Foo() { return "bar"; }});
138    });
139    result.push(function(jsonObject){
140        return jsonObject.stringify({get Foo() { this.foo="wibble"; return "bar"; }});
141    });
142    result.push(function(jsonObject){
143        var count = 0;
144        jsonObject.stringify({get Foo() { count++; return "bar"; }});
145        return count;
146    });
147    result.push(function(jsonObject){
148        var count = 0;
149        return jsonObject.stringify({get Foo() { count++; delete this.bar; return "bar"; }, bar: "wibble"});
150    });
151    result.push(function(jsonObject){
152        var count = 0;
153        return jsonObject.stringify({a:"1", b:"2", c:"3", 5:4, 4:5, 2:6, 1:7});
154    });
155    result.push(function(jsonObject){
156        var allString = true;
157        jsonObject.stringify({a:"1", b:"2", c:"3", 5:4, 4:5, 2:6, 1:7}, function(k,v){allString = allString && (typeof k == "string"); return v});
158        return allString;
159    });
160    result.push(function(jsonObject){
161        var allString = true;
162        jsonObject.stringify([1,2,3,4,5], function(k,v){allString = allString && (typeof k == "string"); return v});
163        return allString;
164    });
165    result.push(function(jsonObject){
166        var allString = true;
167        var array = [];
168        return jsonObject.stringify({a:"1", b:"2", c:"3", 5:4, 4:5, 2:6, 1:7}, array);
169    });
170    result.push(function(jsonObject){
171        var allString = true;
172        var array = ["a"];
173        return jsonObject.stringify({get a(){return 1;array[1]="b";array[2]="c"}, b:"2", c:"3"}, array);
174    });
175    result.push(function(jsonObject){
176        var allString = true;
177        var array = [{toString:function(){array[0]='a'; array[1]='c'; array[2]='b'; return 'a'}}];
178        return jsonObject.stringify(simpleObject, array);
179    });
180    result.push(function(jsonObject){
181        var allString = true;
182        var array = [{toString:function(){array[0]='a'; array[1]='c'; array[2]='b'; return 'a'}}];
183        return jsonObject.stringify(simpleObjectWithProto, array);
184    });
185    result.push(function(jsonObject){
186        var allString = true;
187        var array = [1, new Number(2), NaN, Infinity, -Infinity, new String("str")];
188        return jsonObject.stringify({"1":"1","2":"2","NaN":"NaN","Infinity":"Infinity","-Infinity":"-Infinity","str":"str"}, array);
189    });
190    result[result.length - 1].expected = '{"1":"1","2":"2","NaN":"NaN","Infinity":"Infinity","-Infinity":"-Infinity","str":"str"}';
191    result.push(function(jsonObject){
192        var allString = true;
193        var array = ["1","2","3"];
194        return jsonObject.stringify({1:'a', 2:'b', 3:'c'}, array);
195    });
196    result.push(function(jsonObject){
197        var allString = true;
198        var array = ["1","2","3"];
199        return jsonObject.stringify(simpleArray, array);
200    });
201    result.push(function(jsonObject){
202        return jsonObject.stringify(simpleArray, null, "  ");
203    });
204    result.push(function(jsonObject){
205        return jsonObject.stringify(simpleArray, null, 4);
206    });
207    result.push(function(jsonObject){
208        return jsonObject.stringify(simpleArray, null, "ab");
209    });
210    result.push(function(jsonObject){
211        return jsonObject.stringify(simpleArray, null, 4);
212    });
213    result.push(function(jsonObject){
214        return jsonObject.stringify(simpleObject, null, "  ");
215    });
216    result.push(function(jsonObject){
217        return jsonObject.stringify(simpleObject, null, 4);
218    });
219    result.push(function(jsonObject){
220        return jsonObject.stringify(simpleObject, null, "ab");
221    });
222    result.push(function(jsonObject){
223        return jsonObject.stringify(simpleObject, null, 4);
224    });
225    result.push(function(jsonObject){
226        return jsonObject.stringify(simpleObject, null, 10);
227    });
228    result.push(function(jsonObject){
229        return jsonObject.stringify(simpleObject, null, 11);
230    });
231    result[result.length - 1].expected = JSON.stringify(simpleObject, null, 10);
232    result.push(function(jsonObject){
233        return jsonObject.stringify(simpleObject, null, "          ");
234    });
235    result[result.length - 1].expected = JSON.stringify(simpleObject, null, 10);
236    result.push(function(jsonObject){
237        return jsonObject.stringify(simpleObject, null, "           ");
238    });
239    result[result.length - 1].expected = JSON.stringify(simpleObject, null, 10);
240    result.push(function(jsonObject){
241        return jsonObject.stringify(complexArray, null, "  ");
242    });
243    result.push(function(jsonObject){
244        return jsonObject.stringify(complexArray, null, 4);
245    });
246    result.push(function(jsonObject){
247        return jsonObject.stringify(complexArray, null, "ab");
248    });
249    result.push(function(jsonObject){
250        return jsonObject.stringify(complexArray, null, 4);
251    });
252    result.push(function(jsonObject){
253        return jsonObject.stringify(complexObject, null, "  ");
254    });
255    result.push(function(jsonObject){
256        return jsonObject.stringify(complexObject, null, 4);
257    });
258    result.push(function(jsonObject){
259        return jsonObject.stringify(complexObject, null, "ab");
260    });
261    result.push(function(jsonObject){
262        return jsonObject.stringify(complexObject, null, 4);
263    });
264    result.push(function(jsonObject){
265        var allString = true;
266        var array = ["1","2","3"];
267        return jsonObject.stringify(simpleArrayWithProto, array);
268    });
269    result.push(function(jsonObject){
270        return jsonObject.stringify(simpleArrayWithProto, null, "  ");
271    });
272    result.push(function(jsonObject){
273        return jsonObject.stringify(simpleArrayWithProto, null, 4);
274    });
275    result.push(function(jsonObject){
276        return jsonObject.stringify(simpleArrayWithProto, null, "ab");
277    });
278    result.push(function(jsonObject){
279        return jsonObject.stringify(simpleArrayWithProto, null, 4);
280    });
281    result.push(function(jsonObject){
282        return jsonObject.stringify(simpleObjectWithProto, null, "  ");
283    });
284    result.push(function(jsonObject){
285        return jsonObject.stringify(simpleObjectWithProto, null, 4);
286    });
287    result.push(function(jsonObject){
288        return jsonObject.stringify(simpleObjectWithProto, null, "ab");
289    });
290    result.push(function(jsonObject){
291        return jsonObject.stringify(simpleObjectWithProto, null, 4);
292    });
293    result.push(function(jsonObject){
294        return jsonObject.stringify(simpleObjectWithProto, null, 10);
295    });
296    result.push(function(jsonObject){
297        return jsonObject.stringify(simpleObjectWithProto, null, 11);
298    });
299    result[result.length - 1].expected = JSON.stringify(simpleObjectWithProto, null, 10);
300    result.push(function(jsonObject){
301        return jsonObject.stringify(simpleObjectWithProto, null, "          ");
302    });
303    result[result.length - 1].expected = JSON.stringify(simpleObjectWithProto, null, 10);
304    result.push(function(jsonObject){
305        return jsonObject.stringify(simpleObjectWithProto, null, "           ");
306    });
307    result[result.length - 1].expected = JSON.stringify(simpleObjectWithProto, null, 10);
308    result.push(function(jsonObject){
309        return jsonObject.stringify(complexArrayWithProto, null, "  ");
310    });
311    result.push(function(jsonObject){
312        return jsonObject.stringify(complexArrayWithProto, null, 4);
313    });
314    result.push(function(jsonObject){
315        return jsonObject.stringify(complexArrayWithProto, null, "ab");
316    });
317    result.push(function(jsonObject){
318        return jsonObject.stringify(complexArrayWithProto, null, 4);
319    });
320    result.push(function(jsonObject){
321        return jsonObject.stringify(complexObjectWithProto, null, "  ");
322    });
323    result.push(function(jsonObject){
324        return jsonObject.stringify(complexObjectWithProto, null, 4);
325    });
326    result.push(function(jsonObject){
327        return jsonObject.stringify(complexObjectWithProto, null, "ab");
328    });
329    result.push(function(jsonObject){
330        return jsonObject.stringify(complexObjectWithProto, null, 4);
331    });
332    result.push(function(jsonObject){
333        return jsonObject.stringify(objectWithSideEffectGetter);
334    });
335    result.push(function(jsonObject){
336        return jsonObject.stringify(objectWithSideEffectGetterAndProto);
337    });
338    result.push(function(jsonObject){
339        return jsonObject.stringify(arrayWithSideEffectGetter);
340    });
341    result.push(function(jsonObject){
342        return jsonObject.stringify(arrayWithSideEffectGetterAndProto);
343    });
344    var replaceTracker;
345    function replaceFunc(key, value) {
346        replaceTracker += key + "("+(typeof key)+")" + JSON.stringify(value) + ";";
347        return value;
348    }
349    result.push(function(jsonObject){
350        replaceTracker = "";
351        jsonObject.stringify([1,2,3,,,,4,5,6], replaceFunc);
352        return replaceTracker;
353    });
354    result[result.length - 1].expected = '(string)[1,2,3,null,null,null,4,5,6];0(number)1;1(number)2;2(number)3;3(number)undefined;4(number)undefined;5(number)undefined;6(number)4;7(number)5;8(number)6;'
355    result.push(function(jsonObject){
356        replaceTracker = "";
357        jsonObject.stringify({a:"a", b:"b", c:"c", 3: "d", 2: "e", 1: "f"}, replaceFunc);
358        return replaceTracker;
359    });
360    result[result.length - 1].expected = '(string){"1":"f","2":"e","3":"d","a":"a","b":"b","c":"c"};1(string)"f";2(string)"e";3(string)"d";a(string)"a";b(string)"b";c(string)"c";';
361    result.push(function(jsonObject){
362        var count = 0;
363        var array = [{toString:function(){count++; array[0]='a'; array[1]='c'; array[2]='b'; return 'a'}}];
364        jsonObject.stringify(simpleObject, array);
365        return count;
366    });
367    result.push(function(jsonObject){
368        var allString = true;
369        var array = [{toString:function(){array[0]='a'; array[1]='c'; array[2]='b'; return 'a'}}, 'b', 'c'];
370        return jsonObject.stringify(simpleObject, array);
371    });
372    result.push(function(jsonObject){
373        var count = 0;
374        var array = [{toString:function(){count++; array[0]='a'; array[1]='c'; array[2]='b'; return 'a'}}, 'b', 'c'];
375        jsonObject.stringify(simpleObject, array);
376        return count;
377    });
378    result.push(function(jsonObject){
379        return jsonObject.stringify({a:"1", get b() { this.a="foo"; return "getter"; }, c:"3"});
380    });
381    result.push(function(jsonObject){
382        return jsonObject.stringify({a:"1", get b() { this.c="foo"; return "getter"; }, c:"3"});
383    });
384    result.push(function(jsonObject){
385        var setterCalled = false;
386        jsonObject.stringify({a:"1", set b(s) { setterCalled = true; return "setter"; }, c:"3"});
387        return setterCalled;
388    });
389    result.push(function(jsonObject){
390        return jsonObject.stringify({a:"1", get b(){ return "getter"; }, set b(s) { return "setter"; }, c:"3"});
391    });
392    result.push(function(jsonObject){
393        return jsonObject.stringify(new Array(10));
394    });
395    result.push(function(jsonObject){
396        return jsonObject.stringify([undefined,,null,0,false]);
397    });
398    result.push(function(jsonObject){
399        return jsonObject.stringify({p1:undefined,p2:null,p3:0,p4:false});
400    });
401    var cycleTracker = "";
402    var cyclicObject = { get preSelf1() { cycleTracker+="preSelf1,"; return "preSelf1"; },
403                             preSelf2: {toJSON:function(){cycleTracker+="preSelf2,"; return "preSelf2"}},
404                             self: [],
405                         get postSelf1() { cycleTracker+="postSelf1,"; return "postSelf1" },
406                             postSelf2: {toJSON:function(){cycleTracker+="postSelf2,"; return "postSelf2"}},
407                             toJSON : function(key) { cycleTracker += key + "("+(typeof key)+"):" + this; return this; }
408                       };
409    cyclicObject.self = cyclicObject;
410    result.push(function(jsonObject){
411        cycleTracker = "";
412        return jsonObject.stringify(cyclicObject);
413    });
414    result[result.length - 1].throws = true;
415    result.push(function(jsonObject){
416        cycleTracker = "";
417        try { jsonObject.stringify(cyclicObject); } catch(e) { cycleTracker += " -> exception" }
418        return cycleTracker;
419    });
420    result[result.length - 1].expected = "(string):[object Object]preSelf1,preSelf2,self(string):[object Object] -> exception"
421    var cyclicArray = [{toJSON : function(key,value) { cycleTracker += key + "("+(typeof key)+"):" + this; cycleTracker += "first,"; return this; }},
422                       cyclicArray,
423                       {toJSON : function(key,value) { cycleTracker += key + "("+(typeof key)+"):" + this; cycleTracker += "second,"; return this; }}];
424    cyclicArray[1] = cyclicArray;
425    result.push(function(jsonObject){
426        cycleTracker = "";
427        return jsonObject.stringify(cyclicArray);
428    });
429    result[result.length - 1].throws = true;
430    result.push(function(jsonObject){
431        cycleTracker = "";
432        try { jsonObject.stringify(cyclicArray); } catch(e) { cycleTracker += " -> exception" }
433        return cycleTracker;
434    });
435    result[result.length - 1].expected = "0(number):[object Object]first, -> exception";
436    function createArray(len, o) { var r = []; for (var i = 0; i < len; i++) r[i] = o; return r; }
437    var getterCalls;
438    var magicObject = createArray(10, {abcdefg: [1,2,5,"ab", null, undefined, true, false,,],
439                                       get calls() {return ++getterCalls; },
440                                       "123":createArray(15, "foo"),
441                                       "":{a:"b"}});
442    result.push(function(jsonObject){
443        getterCalls = 0;
444        return jsonObject.stringify(magicObject) + " :: getter calls = " + getterCalls;
445    });
446    result.push(function(jsonObject){
447        return jsonObject.stringify(undefined);
448    });
449    result.push(function(jsonObject){
450        return jsonObject.stringify(null);
451    });
452    result.push(function(jsonObject){
453        return jsonObject.stringify({toJSON:function(){ return undefined; }});
454    });
455    result.push(function(jsonObject){
456        return jsonObject.stringify({toJSON:function(){ return null; }});
457    });
458    result.push(function(jsonObject){
459        return jsonObject.stringify([{toJSON:function(){ return undefined; }}]);
460    });
461    result.push(function(jsonObject){
462        return jsonObject.stringify([{toJSON:function(){ return null; }}]);
463    });
464    result.push(function(jsonObject){
465        return jsonObject.stringify({a:{toJSON:function(){ return undefined; }}});
466    });
467    result.push(function(jsonObject){
468        return jsonObject.stringify({a:{toJSON:function(){ return null; }}});
469    });
470    result.push(function(jsonObject){
471        return jsonObject.stringify({a:{toJSON:function(){ return function(){}; }}});
472    });
473    result.push(function(jsonObject){
474        return jsonObject.stringify({a:function(){}});
475    });
476    result.push(function(jsonObject){
477        var deepObject = {};
478        for (var i = 0; i < 1024; i++)
479            deepObject = {next:deepObject};
480        return jsonObject.stringify(deepObject);
481    });
482    result.push(function(jsonObject){
483        var deepArray = [];
484        for (var i = 0; i < 1024; i++)
485            deepArray = [deepArray];
486        return jsonObject.stringify(deepArray);
487    });
488    result.push(function(jsonObject){
489        var depth = 0;
490        function toDeepVirtualJSONObject() {
491            if (++depth >= 1024)
492                return {};
493            var r = {};
494            r.toJSON = toDeepVirtualJSONObject;
495            return {recurse: r};
496        }
497        return jsonObject.stringify(toDeepVirtualJSONObject());
498    });
499    result.push(function(jsonObject){
500        var depth = 0;
501        function toDeepVirtualJSONArray() {
502            if (++depth >= 1024)
503                return [];
504            var r = [];
505            r.toJSON = toDeepJSONArray;
506            return [r];
507        }
508        return jsonObject.stringify(toDeepVirtualJSONArray());
509    });
510    var fullCharsetString = "";
511    for (var i = 0; i < 65536; i++)
512        fullCharsetString += String.fromCharCode(i);
513    result.push(function(jsonObject){
514        return jsonObject.stringify(fullCharsetString);
515    });
516    return result;
517}
518var tests = createTests();
519for (var i = 0; i < tests.length; i++) {
520    try {
521        debug(tests[i]);
522        if (tests[i].throws)
523            shouldThrow('tests[i](nativeJSON)');
524        else if (tests[i].expected)
525            shouldBe('tests[i](nativeJSON)',  "tests[i].expected");
526        else
527            shouldBe('tests[i](nativeJSON)',  "tests[i](JSON)");
528    }catch(e){}
529}
530