PageRenderTime 76ms CodeModel.GetById 16ms app.highlight 52ms RepoModel.GetById 2ms app.codeStats 0ms

/js/src/json.cpp

http://github.com/zpao/v8monkey
C++ | 945 lines | 627 code | 119 blank | 199 comment | 161 complexity | 5fb6abcd114cf7a38a1a3aac9bb50fdb MD5 | raw file
  1/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  2 * vim: set ts=8 sw=4 et tw=99:
  3 *
  4 * ***** BEGIN LICENSE BLOCK *****
  5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  6 *
  7 * The contents of this file are subject to the Mozilla Public License Version
  8 * 1.1 (the "License"); you may not use this file except in compliance with
  9 * the License. You may obtain a copy of the License at
 10 * http://www.mozilla.org/MPL/
 11 *
 12 * Software distributed under the License is distributed on an "AS IS" basis,
 13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 14 * for the specific language governing rights and limitations under the
 15 * License.
 16 *
 17 * The Original Code is SpiderMonkey JSON.
 18 *
 19 * The Initial Developer of the Original Code is
 20 * Mozilla Corporation.
 21 * Portions created by the Initial Developer are Copyright (C) 1998-1999
 22 * the Initial Developer. All Rights Reserved.
 23 *
 24 * Contributor(s):
 25 *   Robert Sayre <sayrer@gmail.com>
 26 *   Dave Camp <dcamp@mozilla.com>
 27 *
 28 * Alternatively, the contents of this file may be used under the terms of
 29 * either of the GNU General Public License Version 2 or later (the "GPL"),
 30 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 31 * in which case the provisions of the GPL or the LGPL are applicable instead
 32 * of those above. If you wish to allow use of your version of this file only
 33 * under the terms of either the GPL or the LGPL, and not to allow others to
 34 * use your version of this file under the terms of the MPL, indicate your
 35 * decision by deleting the provisions above and replace them with the notice
 36 * and other provisions required by the GPL or the LGPL. If you do not delete
 37 * the provisions above, a recipient may use your version of this file under
 38 * the terms of any one of the MPL, the GPL or the LGPL.
 39 *
 40 * ***** END LICENSE BLOCK ***** */
 41
 42#include <string.h>
 43#include "jsapi.h"
 44#include "jsarray.h"
 45#include "jsatom.h"
 46#include "jsbool.h"
 47#include "jscntxt.h"
 48#include "jsfun.h"
 49#include "jsinterp.h"
 50#include "jsiter.h"
 51#include "jsnum.h"
 52#include "jsobj.h"
 53#include "json.h"
 54#include "jsonparser.h"
 55#include "jsprf.h"
 56#include "jsstr.h"
 57#include "jstypes.h"
 58#include "jsutil.h"
 59#include "jsxml.h"
 60
 61#include "frontend/TokenStream.h"
 62
 63#include "jsatominlines.h"
 64#include "jsboolinlines.h"
 65#include "jsinferinlines.h"
 66#include "jsobjinlines.h"
 67#include "jsstrinlines.h"
 68
 69#include "vm/Stack-inl.h"
 70
 71using namespace js;
 72using namespace js::gc;
 73using namespace js::types;
 74
 75Class js::JSONClass = {
 76    js_JSON_str,
 77    JSCLASS_HAS_CACHED_PROTO(JSProto_JSON),
 78    JS_PropertyStub,        /* addProperty */
 79    JS_PropertyStub,        /* delProperty */
 80    JS_PropertyStub,        /* getProperty */
 81    JS_StrictPropertyStub,  /* setProperty */
 82    JS_EnumerateStub,
 83    JS_ResolveStub,
 84    JS_ConvertStub
 85};
 86
 87/* ES5 15.12.2. */
 88JSBool
 89js_json_parse(JSContext *cx, uintN argc, Value *vp)
 90{
 91    /* Step 1. */
 92    JSLinearString *linear;
 93    if (argc >= 1) {
 94        JSString *str = ToString(cx, vp[2]);
 95        if (!str)
 96            return false;
 97        linear = str->ensureLinear(cx);
 98        if (!linear)
 99            return false;
100    } else {
101        linear = cx->runtime->atomState.typeAtoms[JSTYPE_VOID];
102    }
103    JS::Anchor<JSString *> anchor(linear);
104
105    Value reviver = (argc >= 2) ? vp[3] : UndefinedValue();
106
107    /* Steps 2-5. */
108    return ParseJSONWithReviver(cx, linear->chars(), linear->length(), reviver, vp);
109}
110
111/* ES5 15.12.3. */
112JSBool
113js_json_stringify(JSContext *cx, uintN argc, Value *vp)
114{
115    *vp = (argc >= 1) ? vp[2] : UndefinedValue();
116    JSObject *replacer = (argc >= 2 && vp[3].isObject())
117                         ? &vp[3].toObject()
118                         : NULL;
119    Value space = (argc >= 3) ? vp[4] : UndefinedValue();
120
121    StringBuffer sb(cx);
122    if (!js_Stringify(cx, vp, replacer, space, sb))
123        return false;
124
125    // XXX This can never happen to nsJSON.cpp, but the JSON object
126    // needs to support returning undefined. So this is a little awkward
127    // for the API, because we want to support streaming writers.
128    if (!sb.empty()) {
129        JSString *str = sb.finishString();
130        if (!str)
131            return false;
132        vp->setString(str);
133    } else {
134        vp->setUndefined();
135    }
136
137    return true;
138}
139
140static inline bool IsQuoteSpecialCharacter(jschar c)
141{
142    JS_STATIC_ASSERT('\b' < ' ');
143    JS_STATIC_ASSERT('\f' < ' ');
144    JS_STATIC_ASSERT('\n' < ' ');
145    JS_STATIC_ASSERT('\r' < ' ');
146    JS_STATIC_ASSERT('\t' < ' ');
147    return c == '"' || c == '\\' || c < ' ';
148}
149
150/* ES5 15.12.3 Quote. */
151static bool
152Quote(JSContext *cx, StringBuffer &sb, JSString *str)
153{
154    JS::Anchor<JSString *> anchor(str);
155    size_t len = str->length();
156    const jschar *buf = str->getChars(cx);
157    if (!buf)
158        return false;
159
160    /* Step 1. */
161    if (!sb.append('"'))
162        return false;
163
164    /* Step 2. */
165    for (size_t i = 0; i < len; ++i) {
166        /* Batch-append maximal character sequences containing no escapes. */
167        size_t mark = i;
168        do {
169            if (IsQuoteSpecialCharacter(buf[i]))
170                break;
171        } while (++i < len);
172        if (i > mark) {
173            if (!sb.append(&buf[mark], i - mark))
174                return false;
175            if (i == len)
176                break;
177        }
178
179        jschar c = buf[i];
180        if (c == '"' || c == '\\') {
181            if (!sb.append('\\') || !sb.append(c))
182                return false;
183        } else if (c == '\b' || c == '\f' || c == '\n' || c == '\r' || c == '\t') {
184           jschar abbrev = (c == '\b')
185                         ? 'b'
186                         : (c == '\f')
187                         ? 'f'
188                         : (c == '\n')
189                         ? 'n'
190                         : (c == '\r')
191                         ? 'r'
192                         : 't';
193           if (!sb.append('\\') || !sb.append(abbrev))
194               return false;
195        } else {
196            JS_ASSERT(c < ' ');
197            if (!sb.append("\\u00"))
198                return false;
199            JS_ASSERT((c >> 4) < 10);
200            uint8_t x = c >> 4, y = c % 16;
201            if (!sb.append('0' + x) || !sb.append(y < 10 ? '0' + y : 'a' + (y - 10)))
202                return false;
203        }
204    }
205
206    /* Steps 3-4. */
207    return sb.append('"');
208}
209
210class StringifyContext
211{
212  public:
213    StringifyContext(JSContext *cx, StringBuffer &sb, const StringBuffer &gap,
214                     JSObject *replacer, const AutoIdVector &propertyList)
215      : sb(sb),
216        gap(gap),
217        replacer(replacer),
218        propertyList(propertyList),
219        depth(0),
220        objectStack(cx)
221    {}
222
223    bool init() {
224        return objectStack.init(16);
225    }
226
227#ifdef DEBUG
228    ~StringifyContext() { JS_ASSERT(objectStack.empty()); }
229#endif
230
231    StringBuffer &sb;
232    const StringBuffer &gap;
233    JSObject * const replacer;
234    const AutoIdVector &propertyList;
235    uint32_t depth;
236    HashSet<JSObject *> objectStack;
237};
238
239static JSBool Str(JSContext *cx, const Value &v, StringifyContext *scx);
240
241static JSBool
242WriteIndent(JSContext *cx, StringifyContext *scx, uint32_t limit)
243{
244    if (!scx->gap.empty()) {
245        if (!scx->sb.append('\n'))
246            return JS_FALSE;
247        for (uint32_t i = 0; i < limit; i++) {
248            if (!scx->sb.append(scx->gap.begin(), scx->gap.end()))
249                return JS_FALSE;
250        }
251    }
252
253    return JS_TRUE;
254}
255
256class CycleDetector
257{
258  public:
259    CycleDetector(StringifyContext *scx, JSObject *obj)
260      : objectStack(scx->objectStack), obj(obj) {
261    }
262
263    bool init(JSContext *cx) {
264        HashSet<JSObject *>::AddPtr ptr = objectStack.lookupForAdd(obj);
265        if (ptr) {
266            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CYCLIC_VALUE, js_object_str);
267            return false;
268        }
269        return objectStack.add(ptr, obj);
270    }
271
272    ~CycleDetector() {
273        objectStack.remove(obj);
274    }
275
276  private:
277    HashSet<JSObject *> &objectStack;
278    JSObject *const obj;
279};
280
281template<typename KeyType>
282class KeyStringifier {
283};
284
285template<>
286class KeyStringifier<uint32_t> {
287  public:
288    static JSString *toString(JSContext *cx, uint32_t index) {
289        return IndexToString(cx, index);
290    }
291};
292
293template<>
294class KeyStringifier<jsid> {
295  public:
296    static JSString *toString(JSContext *cx, jsid id) {
297        return IdToString(cx, id);
298    }
299};
300
301/*
302 * ES5 15.12.3 Str, steps 2-4, extracted to enable preprocessing of property
303 * values when stringifying objects in JO.
304 */
305template<typename KeyType>
306static bool
307PreprocessValue(JSContext *cx, JSObject *holder, KeyType key, Value *vp, StringifyContext *scx)
308{
309    JSString *keyStr = NULL;
310
311    /* Step 2. */
312    if (vp->isObject()) {
313        Value toJSON;
314        jsid id = ATOM_TO_JSID(cx->runtime->atomState.toJSONAtom);
315        if (!js_GetMethod(cx, &vp->toObject(), id, JSGET_NO_METHOD_BARRIER, &toJSON))
316            return false;
317
318        if (js_IsCallable(toJSON)) {
319            keyStr = KeyStringifier<KeyType>::toString(cx, key);
320            if (!keyStr)
321                return false;
322
323            InvokeArgsGuard args;
324            if (!cx->stack.pushInvokeArgs(cx, 1, &args))
325                return false;
326
327            args.calleev() = toJSON;
328            args.thisv() = *vp;
329            args[0] = StringValue(keyStr);
330
331            if (!Invoke(cx, args))
332                return false;
333            *vp = args.rval();
334        }
335    }
336
337    /* Step 3. */
338    if (scx->replacer && scx->replacer->isCallable()) {
339        if (!keyStr) {
340            keyStr = KeyStringifier<KeyType>::toString(cx, key);
341            if (!keyStr)
342                return false;
343        }
344
345        InvokeArgsGuard args;
346        if (!cx->stack.pushInvokeArgs(cx, 2, &args))
347            return false;
348
349        args.calleev() = ObjectValue(*scx->replacer);
350        args.thisv() = ObjectValue(*holder);
351        args[0] = StringValue(keyStr);
352        args[1] = *vp;
353
354        if (!Invoke(cx, args))
355            return false;
356        *vp = args.rval();
357    }
358
359    /* Step 4. */
360    if (vp->isObject()) {
361        JSObject &obj = vp->toObject();
362        if (ObjectClassIs(obj, ESClass_Number, cx)) {
363            double d;
364            if (!ToNumber(cx, *vp, &d))
365                return false;
366            vp->setNumber(d);
367        } else if (ObjectClassIs(obj, ESClass_String, cx)) {
368            JSString *str = ToStringSlow(cx, *vp);
369            if (!str)
370                return false;
371            vp->setString(str);
372        } else if (ObjectClassIs(obj, ESClass_Boolean, cx)) {
373            if (!BooleanGetPrimitiveValue(cx, obj, vp))
374                return false;
375            JS_ASSERT(vp->isBoolean());
376        }
377    }
378
379    return true;
380}
381
382/*
383 * Determines whether a value which has passed by ES5 150.2.3 Str steps 1-4's
384 * gauntlet will result in Str returning |undefined|.  This function is used to
385 * properly omit properties resulting in such values when stringifying objects,
386 * while properly stringifying such properties as null when they're encountered
387 * in arrays.
388 */
389static inline bool
390IsFilteredValue(const Value &v)
391{
392    return v.isUndefined() || js_IsCallable(v) || (v.isObject() && v.toObject().isXML());
393}
394
395/* ES5 15.12.3 JO. */
396static JSBool
397JO(JSContext *cx, JSObject *obj, StringifyContext *scx)
398{
399    /*
400     * This method implements the JO algorithm in ES5 15.12.3, but:
401     *
402     *   * The algorithm is somewhat reformulated to allow the final string to
403     *     be streamed into a single buffer, rather than be created and copied
404     *     into place incrementally as the ES5 algorithm specifies it.  This
405     *     requires moving portions of the Str call in 8a into this algorithm
406     *     (and in JA as well).
407     */
408
409    /* Steps 1-2, 11. */
410    CycleDetector detect(scx, obj);
411    if (!detect.init(cx))
412        return JS_FALSE;
413
414    if (!scx->sb.append('{'))
415        return JS_FALSE;
416
417    /* Steps 5-7. */
418    Maybe<AutoIdVector> ids;
419    const AutoIdVector *props;
420    if (scx->replacer && !scx->replacer->isCallable()) {
421        JS_ASSERT(JS_IsArrayObject(cx, scx->replacer));
422        props = &scx->propertyList;
423    } else {
424        JS_ASSERT_IF(scx->replacer, scx->propertyList.length() == 0);
425        ids.construct(cx);
426        if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, ids.addr()))
427            return false;
428        props = ids.addr();
429    }
430
431    /* My kingdom for not-quite-initialized-from-the-start references. */
432    const AutoIdVector &propertyList = *props;
433
434    /* Steps 8-10, 13. */
435    bool wroteMember = false;
436    for (size_t i = 0, len = propertyList.length(); i < len; i++) {
437        /*
438         * Steps 8a-8b.  Note that the call to Str is broken up into 1) getting
439         * the property; 2) processing for toJSON, calling the replacer, and
440         * handling boxed Number/String/Boolean objects; 3) filtering out
441         * values which process to |undefined|, and 4) stringifying all values
442         * which pass the filter.
443         */
444        const jsid &id = propertyList[i];
445        Value outputValue;
446        if (!obj->getGeneric(cx, id, &outputValue))
447            return false;
448        if (!PreprocessValue(cx, obj, id, &outputValue, scx))
449            return false;
450        if (IsFilteredValue(outputValue))
451            continue;
452
453        /* Output a comma unless this is the first member to write. */
454        if (wroteMember && !scx->sb.append(','))
455            return false;
456        wroteMember = true;
457
458        if (!WriteIndent(cx, scx, scx->depth))
459            return false;
460
461        JSString *s = IdToString(cx, id);
462        if (!s)
463            return false;
464
465        if (!Quote(cx, scx->sb, s) ||
466            !scx->sb.append(':') ||
467            !(scx->gap.empty() || scx->sb.append(' ')) ||
468            !Str(cx, outputValue, scx))
469        {
470            return false;
471        }
472    }
473
474    if (wroteMember && !WriteIndent(cx, scx, scx->depth - 1))
475        return false;
476
477    return scx->sb.append('}');
478}
479
480/* ES5 15.12.3 JA. */
481static JSBool
482JA(JSContext *cx, JSObject *obj, StringifyContext *scx)
483{
484    /*
485     * This method implements the JA algorithm in ES5 15.12.3, but:
486     *
487     *   * The algorithm is somewhat reformulated to allow the final string to
488     *     be streamed into a single buffer, rather than be created and copied
489     *     into place incrementally as the ES5 algorithm specifies it.  This
490     *     requires moving portions of the Str call in 8a into this algorithm
491     *     (and in JO as well).
492     */
493
494    /* Steps 1-2, 11. */
495    CycleDetector detect(scx, obj);
496    if (!detect.init(cx))
497        return JS_FALSE;
498
499    if (!scx->sb.append('['))
500        return JS_FALSE;
501
502    /* Step 6. */
503    jsuint length;
504    if (!js_GetLengthProperty(cx, obj, &length))
505        return JS_FALSE;
506
507    /* Steps 7-10. */
508    if (length != 0) {
509        /* Steps 4, 10b(i). */
510        if (!WriteIndent(cx, scx, scx->depth))
511            return JS_FALSE;
512
513        /* Steps 7-10. */
514        Value outputValue;
515        for (uint32_t i = 0; i < length; i++) {
516            /*
517             * Steps 8a-8c.  Again note how the call to the spec's Str method
518             * is broken up into getting the property, running it past toJSON
519             * and the replacer and maybe unboxing, and interpreting some
520             * values as |null| in separate steps.
521             */
522            if (!obj->getElement(cx, i, &outputValue))
523                return JS_FALSE;
524            if (!PreprocessValue(cx, obj, i, &outputValue, scx))
525                return JS_FALSE;
526            if (IsFilteredValue(outputValue)) {
527                if (!scx->sb.append("null"))
528                    return JS_FALSE;
529            } else {
530                if (!Str(cx, outputValue, scx))
531                    return JS_FALSE;
532            }
533
534            /* Steps 3, 4, 10b(i). */
535            if (i < length - 1) {
536                if (!scx->sb.append(','))
537                    return JS_FALSE;
538                if (!WriteIndent(cx, scx, scx->depth))
539                    return JS_FALSE;
540            }
541        }
542
543        /* Step 10(b)(iii). */
544        if (!WriteIndent(cx, scx, scx->depth - 1))
545            return JS_FALSE;
546    }
547
548    return scx->sb.append(']');
549}
550
551static JSBool
552Str(JSContext *cx, const Value &v, StringifyContext *scx)
553{
554    /* Step 11 must be handled by the caller. */
555    JS_ASSERT(!IsFilteredValue(v));
556
557    JS_CHECK_RECURSION(cx, return false);
558
559    /*
560     * This method implements the Str algorithm in ES5 15.12.3, but:
561     *
562     *   * We move property retrieval (step 1) into callers to stream the
563     *     stringification process and avoid constantly copying strings.
564     *   * We move the preprocessing in steps 2-4 into a helper function to
565     *     allow both JO and JA to use this method.  While JA could use it
566     *     without this move, JO must omit any |undefined|-valued property per
567     *     so it can't stream out a value using the Str method exactly as
568     *     defined by ES5.
569     *   * We move step 11 into callers, again to ease streaming.
570     */
571
572    /* Step 8. */
573    if (v.isString())
574        return Quote(cx, scx->sb, v.toString());
575
576    /* Step 5. */
577    if (v.isNull())
578        return scx->sb.append("null");
579
580    /* Steps 6-7. */
581    if (v.isBoolean())
582        return v.toBoolean() ? scx->sb.append("true") : scx->sb.append("false");
583
584    /* Step 9. */
585    if (v.isNumber()) {
586        if (v.isDouble()) {
587            if (!JSDOUBLE_IS_FINITE(v.toDouble()))
588                return scx->sb.append("null");
589        }
590
591        StringBuffer sb(cx);
592        if (!NumberValueToStringBuffer(cx, v, sb))
593            return false;
594
595        return scx->sb.append(sb.begin(), sb.length());
596    }
597
598    /* Step 10. */
599    JS_ASSERT(v.isObject());
600    JSObject *obj = &v.toObject();
601
602    scx->depth++;
603    JSBool ok;
604    if (ObjectClassIs(v.toObject(), ESClass_Array, cx))
605        ok = JA(cx, obj, scx);
606    else
607        ok = JO(cx, obj, scx);
608    scx->depth--;
609
610    return ok;
611}
612
613/* ES5 15.12.3. */
614JSBool
615js_Stringify(JSContext *cx, Value *vp, JSObject *replacer, Value space, StringBuffer &sb)
616{
617    /* Step 4. */
618    AutoIdVector propertyList(cx);
619    if (replacer) {
620        if (replacer->isCallable()) {
621            /* Step 4a(i): use replacer to transform values.  */
622        } else if (ObjectClassIs(*replacer, ESClass_Array, cx)) {
623            /*
624             * Step 4b: The spec algorithm is unhelpfully vague about the exact
625             * steps taken when the replacer is an array, regarding the exact
626             * sequence of [[Get]] calls for the array's elements, when its
627             * overall length is calculated, whether own or own plus inherited
628             * properties are considered, and so on.  A rewrite was proposed in
629             * <https://mail.mozilla.org/pipermail/es5-discuss/2011-April/003976.html>,
630             * whose steps are copied below, and which are implemented here.
631             *
632             * i.   Let PropertyList be an empty internal List.
633             * ii.  Let len be the result of calling the [[Get]] internal
634             *      method of replacer with the argument "length".
635             * iii. Let i be 0.
636             * iv.  While i < len:
637             *      1. Let item be undefined.
638             *      2. Let v be the result of calling the [[Get]] internal
639             *         method of replacer with the argument ToString(i).
640             *      3. If Type(v) is String then let item be v.
641             *      4. Else if Type(v) is Number then let item be ToString(v).
642             *      5. Else if Type(v) is Object then
643             *         a. If the [[Class]] internal property of v is "String"
644             *            or "Number" then let item be ToString(v).
645             *      6. If item is not undefined and item is not currently an
646             *         element of PropertyList then,
647             *         a. Append item to the end of PropertyList.
648             *      7. Let i be i + 1.
649             */
650
651            /* Step 4b(ii). */
652            jsuint len;
653            JS_ALWAYS_TRUE(js_GetLengthProperty(cx, replacer, &len));
654            if (replacer->isDenseArray())
655                len = JS_MIN(len, replacer->getDenseArrayCapacity());
656
657            HashSet<jsid> idSet(cx);
658            if (!idSet.init(len))
659                return false;
660
661            /* Step 4b(iii). */
662            jsuint i = 0;
663
664            /* Step 4b(iv). */
665            for (; i < len; i++) {
666                /* Step 4b(iv)(2). */
667                Value v;
668                if (!replacer->getElement(cx, i, &v))
669                    return false;
670
671                jsid id;
672                if (v.isNumber()) {
673                    /* Step 4b(iv)(4). */
674                    int32_t n;
675                    if (v.isNumber() && ValueFitsInInt32(v, &n) && INT_FITS_IN_JSID(n)) {
676                        id = INT_TO_JSID(n);
677                    } else {
678                        if (!js_ValueToStringId(cx, v, &id))
679                            return false;
680                        id = js_CheckForStringIndex(id);
681                    }
682                } else if (v.isString() ||
683                           (v.isObject() &&
684                            (ObjectClassIs(v.toObject(), ESClass_String, cx) ||
685                             ObjectClassIs(v.toObject(), ESClass_Number, cx))))
686                {
687                    /* Step 4b(iv)(3), 4b(iv)(5). */
688                    if (!js_ValueToStringId(cx, v, &id))
689                        return false;
690                    id = js_CheckForStringIndex(id);
691                } else {
692                    continue;
693                }
694
695                /* Step 4b(iv)(6). */
696                HashSet<jsid>::AddPtr p = idSet.lookupForAdd(id);
697                if (!p) {
698                    /* Step 4b(iv)(6)(a). */
699                    if (!idSet.add(p, id) || !propertyList.append(id))
700                        return false;
701                }
702            }
703        } else {
704            replacer = NULL;
705        }
706    }
707
708    /* Step 5. */
709    if (space.isObject()) {
710        JSObject &spaceObj = space.toObject();
711        if (ObjectClassIs(spaceObj, ESClass_Number, cx)) {
712            jsdouble d;
713            if (!ToNumber(cx, space, &d))
714                return false;
715            space = NumberValue(d);
716        } else if (ObjectClassIs(spaceObj, ESClass_String, cx)) {
717            JSString *str = ToStringSlow(cx, space);
718            if (!str)
719                return false;
720            space = StringValue(str);
721        }
722    }
723
724    StringBuffer gap(cx);
725
726    if (space.isNumber()) {
727        /* Step 6. */
728        jsdouble d;
729        JS_ALWAYS_TRUE(ToInteger(cx, space, &d));
730        d = JS_MIN(10, d);
731        if (d >= 1 && !gap.appendN(' ', uint32_t(d)))
732            return false;
733    } else if (space.isString()) {
734        /* Step 7. */
735        JSLinearString *str = space.toString()->ensureLinear(cx);
736        if (!str)
737            return false;
738        JS::Anchor<JSString *> anchor(str);
739        size_t len = JS_MIN(10, space.toString()->length());
740        if (!gap.append(str->chars(), len))
741            return false;
742    } else {
743        /* Step 8. */
744        JS_ASSERT(gap.empty());
745    }
746
747    /* Step 9. */
748    JSObject *wrapper = NewBuiltinClassInstance(cx, &ObjectClass);
749    if (!wrapper)
750        return false;
751
752    /* Step 10. */
753    jsid emptyId = ATOM_TO_JSID(cx->runtime->atomState.emptyAtom);
754    if (!DefineNativeProperty(cx, wrapper, emptyId, *vp, JS_PropertyStub, JS_StrictPropertyStub,
755                              JSPROP_ENUMERATE, 0, 0))
756    {
757        return false;
758    }
759
760    /* Step 11. */
761    StringifyContext scx(cx, sb, gap, replacer, propertyList);
762    if (!scx.init())
763        return false;
764
765    if (!PreprocessValue(cx, wrapper, emptyId, vp, &scx))
766        return false;
767    if (IsFilteredValue(*vp))
768        return true;
769
770    return Str(cx, *vp, &scx);
771}
772
773/* ES5 15.12.2 Walk. */
774static bool
775Walk(JSContext *cx, JSObject *holder, jsid name, const Value &reviver, Value *vp)
776{
777    JS_CHECK_RECURSION(cx, return false);
778
779    /* Step 1. */
780    Value val;
781    if (!holder->getGeneric(cx, name, &val))
782        return false;
783
784    /* Step 2. */
785    if (val.isObject()) {
786        JSObject *obj = &val.toObject();
787
788        /* 'val' must have been produced by the JSON parser, so not a proxy. */
789        JS_ASSERT(!obj->isProxy());
790        if (obj->isArray()) {
791            /* Step 2a(ii). */
792            uint32_t length = obj->getArrayLength();
793
794            /* Step 2a(i), 2a(iii-iv). */
795            for (uint32_t i = 0; i < length; i++) {
796                jsid id;
797                if (!IndexToId(cx, i, &id))
798                    return false;
799
800                /* Step 2a(iii)(1). */
801                Value newElement;
802                if (!Walk(cx, obj, id, reviver, &newElement))
803                    return false;
804
805                /*
806                 * Arrays which begin empty and whose properties are always
807                 * incrementally appended are always dense, no matter their
808                 * length, under current dense/slow array heuristics.
809                 * Also, deleting a property from a dense array which is not
810                 * currently being enumerated never makes it slow.  This array
811                 * is never exposed until the reviver sees it below, so it must
812                 * be dense and isn't currently being enumerated.  Therefore
813                 * property definition and deletion will always succeed,
814                 * and we need not check for failure.
815                 */
816                if (newElement.isUndefined()) {
817                    /* Step 2a(iii)(2). */
818                    JS_ALWAYS_TRUE(array_deleteElement(cx, obj, i, &newElement, false));
819                } else {
820                    /* Step 2a(iii)(3). */
821                    JS_ALWAYS_TRUE(array_defineElement(cx, obj, i, &newElement, JS_PropertyStub,
822                                                       JS_StrictPropertyStub, JSPROP_ENUMERATE));
823                }
824            }
825        } else {
826            /* Step 2b(i). */
827            AutoIdVector keys(cx);
828            if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, &keys))
829                return false;
830
831            /* Step 2b(ii). */
832            for (size_t i = 0, len = keys.length(); i < len; i++) {
833                /* Step 2b(ii)(1). */
834                Value newElement;
835                jsid id = keys[i];
836                if (!Walk(cx, obj, id, reviver, &newElement))
837                    return false;
838
839                if (newElement.isUndefined()) {
840                    /* Step 2b(ii)(2). */
841                    if (!obj->deleteByValue(cx, IdToValue(id), &newElement, false))
842                        return false;
843                } else {
844                    /* Step 2b(ii)(3). */
845                    JS_ASSERT(obj->isNative());
846                    if (!DefineNativeProperty(cx, obj, id, newElement, JS_PropertyStub,
847                                              JS_StrictPropertyStub, JSPROP_ENUMERATE, 0, 0))
848                    {
849                        return false;
850                    }
851                }
852            }
853        }
854    }
855
856    /* Step 3. */
857    JSString *key = IdToString(cx, name);
858    if (!key)
859        return false;
860
861    InvokeArgsGuard args;
862    if (!cx->stack.pushInvokeArgs(cx, 2, &args))
863        return false;
864
865    args.calleev() = reviver;
866    args.thisv() = ObjectValue(*holder);
867    args[0] = StringValue(key);
868    args[1] = val;
869
870    if (!Invoke(cx, args))
871        return false;
872    *vp = args.rval();
873    return true;
874}
875
876static bool
877Revive(JSContext *cx, const Value &reviver, Value *vp)
878{
879
880    JSObject *obj = NewBuiltinClassInstance(cx, &ObjectClass);
881    if (!obj)
882        return false;
883
884    if (!obj->defineProperty(cx, cx->runtime->atomState.emptyAtom, *vp))
885        return false;
886
887    return Walk(cx, obj, ATOM_TO_JSID(cx->runtime->atomState.emptyAtom), reviver, vp);
888}
889
890namespace js {
891
892JSBool
893ParseJSONWithReviver(JSContext *cx, const jschar *chars, size_t length, const Value &reviver,
894                     Value *vp, DecodingMode decodingMode /* = STRICT */)
895{
896    /* 15.12.2 steps 2-3. */
897    JSONParser parser(cx, chars, length,
898                      decodingMode == STRICT ? JSONParser::StrictJSON : JSONParser::LegacyJSON);
899    if (!parser.parse(vp))
900        return false;
901
902    /* 15.12.2 steps 4-5. */
903    if (js_IsCallable(reviver))
904        return Revive(cx, reviver, vp);
905    return true;
906}
907
908} /* namespace js */
909
910#if JS_HAS_TOSOURCE
911static JSBool
912json_toSource(JSContext *cx, uintN argc, Value *vp)
913{
914    vp->setString(CLASS_ATOM(cx, JSON));
915    return JS_TRUE;
916}
917#endif
918
919static JSFunctionSpec json_static_methods[] = {
920#if JS_HAS_TOSOURCE
921    JS_FN(js_toSource_str,  json_toSource,      0, 0),
922#endif
923    JS_FN("parse",          js_json_parse,      2, 0),
924    JS_FN("stringify",      js_json_stringify,  3, 0),
925    JS_FS_END
926};
927
928JSObject *
929js_InitJSONClass(JSContext *cx, JSObject *obj)
930{
931    JSObject *JSON = NewObjectWithClassProto(cx, &JSONClass, NULL, obj);
932    if (!JSON || !JSON->setSingletonType(cx))
933        return NULL;
934
935    if (!JS_DefineProperty(cx, obj, js_JSON_str, OBJECT_TO_JSVAL(JSON),
936                           JS_PropertyStub, JS_StrictPropertyStub, 0))
937        return NULL;
938
939    if (!JS_DefineFunctions(cx, JSON, json_static_methods))
940        return NULL;
941
942    MarkStandardClassInitializedNoProto(obj, &JSONClass);
943
944    return JSON;
945}