js/src/json.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     2  * vim: set ts=8 sts=4 et sw=4 tw=99:
     3  * This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include "json.h"
     9 #include "mozilla/FloatingPoint.h"
    11 #include "jsarray.h"
    12 #include "jsatom.h"
    13 #include "jscntxt.h"
    14 #include "jsnum.h"
    15 #include "jsobj.h"
    16 #include "jsonparser.h"
    17 #include "jsstr.h"
    18 #include "jstypes.h"
    19 #include "jsutil.h"
    21 #include "vm/Interpreter.h"
    22 #include "vm/StringBuffer.h"
    24 #include "jsatominlines.h"
    25 #include "jsboolinlines.h"
    26 #include "jsobjinlines.h"
    28 using namespace js;
    29 using namespace js::gc;
    30 using namespace js::types;
    32 using mozilla::IsFinite;
    33 using mozilla::Maybe;
    35 const Class js::JSONClass = {
    36     js_JSON_str,
    37     JSCLASS_HAS_CACHED_PROTO(JSProto_JSON),
    38     JS_PropertyStub,        /* addProperty */
    39     JS_DeletePropertyStub,  /* delProperty */
    40     JS_PropertyStub,        /* getProperty */
    41     JS_StrictPropertyStub,  /* setProperty */
    42     JS_EnumerateStub,
    43     JS_ResolveStub,
    44     JS_ConvertStub
    45 };
    47 static inline bool IsQuoteSpecialCharacter(jschar c)
    48 {
    49     JS_STATIC_ASSERT('\b' < ' ');
    50     JS_STATIC_ASSERT('\f' < ' ');
    51     JS_STATIC_ASSERT('\n' < ' ');
    52     JS_STATIC_ASSERT('\r' < ' ');
    53     JS_STATIC_ASSERT('\t' < ' ');
    54     return c == '"' || c == '\\' || c < ' ';
    55 }
    57 /* ES5 15.12.3 Quote. */
    58 static bool
    59 Quote(JSContext *cx, StringBuffer &sb, JSString *str)
    60 {
    61     JS::Anchor<JSString *> anchor(str);
    62     size_t len = str->length();
    63     const jschar *buf = str->getChars(cx);
    64     if (!buf)
    65         return false;
    67     /* Step 1. */
    68     if (!sb.append('"'))
    69         return false;
    71     /* Step 2. */
    72     for (size_t i = 0; i < len; ++i) {
    73         /* Batch-append maximal character sequences containing no escapes. */
    74         size_t mark = i;
    75         do {
    76             if (IsQuoteSpecialCharacter(buf[i]))
    77                 break;
    78         } while (++i < len);
    79         if (i > mark) {
    80             if (!sb.append(&buf[mark], i - mark))
    81                 return false;
    82             if (i == len)
    83                 break;
    84         }
    86         jschar c = buf[i];
    87         if (c == '"' || c == '\\') {
    88             if (!sb.append('\\') || !sb.append(c))
    89                 return false;
    90         } else if (c == '\b' || c == '\f' || c == '\n' || c == '\r' || c == '\t') {
    91            jschar abbrev = (c == '\b')
    92                          ? 'b'
    93                          : (c == '\f')
    94                          ? 'f'
    95                          : (c == '\n')
    96                          ? 'n'
    97                          : (c == '\r')
    98                          ? 'r'
    99                          : 't';
   100            if (!sb.append('\\') || !sb.append(abbrev))
   101                return false;
   102         } else {
   103             JS_ASSERT(c < ' ');
   104             if (!sb.append("\\u00"))
   105                 return false;
   106             JS_ASSERT((c >> 4) < 10);
   107             uint8_t x = c >> 4, y = c % 16;
   108             if (!sb.append('0' + x) || !sb.append(y < 10 ? '0' + y : 'a' + (y - 10)))
   109                 return false;
   110         }
   111     }
   113     /* Steps 3-4. */
   114     return sb.append('"');
   115 }
   117 namespace {
   119 class StringifyContext
   120 {
   121   public:
   122     StringifyContext(JSContext *cx, StringBuffer &sb, const StringBuffer &gap,
   123                      HandleObject replacer, const AutoIdVector &propertyList)
   124       : sb(sb),
   125         gap(gap),
   126         replacer(cx, replacer),
   127         propertyList(propertyList),
   128         depth(0)
   129     {}
   131     StringBuffer &sb;
   132     const StringBuffer &gap;
   133     RootedObject replacer;
   134     const AutoIdVector &propertyList;
   135     uint32_t depth;
   136 };
   138 } /* anonymous namespace */
   140 static bool Str(JSContext *cx, const Value &v, StringifyContext *scx);
   142 static bool
   143 WriteIndent(JSContext *cx, StringifyContext *scx, uint32_t limit)
   144 {
   145     if (!scx->gap.empty()) {
   146         if (!scx->sb.append('\n'))
   147             return false;
   148         for (uint32_t i = 0; i < limit; i++) {
   149             if (!scx->sb.append(scx->gap.begin(), scx->gap.end()))
   150                 return false;
   151         }
   152     }
   154     return true;
   155 }
   157 namespace {
   159 template<typename KeyType>
   160 class KeyStringifier {
   161 };
   163 template<>
   164 class KeyStringifier<uint32_t> {
   165   public:
   166     static JSString *toString(JSContext *cx, uint32_t index) {
   167         return IndexToString(cx, index);
   168     }
   169 };
   171 template<>
   172 class KeyStringifier<HandleId> {
   173   public:
   174     static JSString *toString(JSContext *cx, HandleId id) {
   175         return IdToString(cx, id);
   176     }
   177 };
   179 } /* anonymous namespace */
   181 /*
   182  * ES5 15.12.3 Str, steps 2-4, extracted to enable preprocessing of property
   183  * values when stringifying objects in JO.
   184  */
   185 template<typename KeyType>
   186 static bool
   187 PreprocessValue(JSContext *cx, HandleObject holder, KeyType key, MutableHandleValue vp, StringifyContext *scx)
   188 {
   189     RootedString keyStr(cx);
   191     /* Step 2. */
   192     if (vp.isObject()) {
   193         RootedValue toJSON(cx);
   194         RootedObject obj(cx, &vp.toObject());
   195         if (!JSObject::getProperty(cx, obj, obj, cx->names().toJSON, &toJSON))
   196             return false;
   198         if (js_IsCallable(toJSON)) {
   199             keyStr = KeyStringifier<KeyType>::toString(cx, key);
   200             if (!keyStr)
   201                 return false;
   203             InvokeArgs args(cx);
   204             if (!args.init(1))
   205                 return false;
   207             args.setCallee(toJSON);
   208             args.setThis(vp);
   209             args[0].setString(keyStr);
   211             if (!Invoke(cx, args))
   212                 return false;
   213             vp.set(args.rval());
   214         }
   215     }
   217     /* Step 3. */
   218     if (scx->replacer && scx->replacer->isCallable()) {
   219         if (!keyStr) {
   220             keyStr = KeyStringifier<KeyType>::toString(cx, key);
   221             if (!keyStr)
   222                 return false;
   223         }
   225         InvokeArgs args(cx);
   226         if (!args.init(2))
   227             return false;
   229         args.setCallee(ObjectValue(*scx->replacer));
   230         args.setThis(ObjectValue(*holder));
   231         args[0].setString(keyStr);
   232         args[1].set(vp);
   234         if (!Invoke(cx, args))
   235             return false;
   236         vp.set(args.rval());
   237     }
   239     /* Step 4. */
   240     if (vp.get().isObject()) {
   241         RootedObject obj(cx, &vp.get().toObject());
   242         if (ObjectClassIs(obj, ESClass_Number, cx)) {
   243             double d;
   244             if (!ToNumber(cx, vp, &d))
   245                 return false;
   246             vp.set(NumberValue(d));
   247         } else if (ObjectClassIs(obj, ESClass_String, cx)) {
   248             JSString *str = ToStringSlow<CanGC>(cx, vp);
   249             if (!str)
   250                 return false;
   251             vp.set(StringValue(str));
   252         } else if (ObjectClassIs(obj, ESClass_Boolean, cx)) {
   253             vp.setBoolean(BooleanGetPrimitiveValue(obj));
   254         }
   255     }
   257     return true;
   258 }
   260 /*
   261  * Determines whether a value which has passed by ES5 150.2.3 Str steps 1-4's
   262  * gauntlet will result in Str returning |undefined|.  This function is used to
   263  * properly omit properties resulting in such values when stringifying objects,
   264  * while properly stringifying such properties as null when they're encountered
   265  * in arrays.
   266  */
   267 static inline bool
   268 IsFilteredValue(const Value &v)
   269 {
   270     return v.isUndefined() || js_IsCallable(v);
   271 }
   273 /* ES5 15.12.3 JO. */
   274 static bool
   275 JO(JSContext *cx, HandleObject obj, StringifyContext *scx)
   276 {
   277     /*
   278      * This method implements the JO algorithm in ES5 15.12.3, but:
   279      *
   280      *   * The algorithm is somewhat reformulated to allow the final string to
   281      *     be streamed into a single buffer, rather than be created and copied
   282      *     into place incrementally as the ES5 algorithm specifies it.  This
   283      *     requires moving portions of the Str call in 8a into this algorithm
   284      *     (and in JA as well).
   285      */
   287     /* Steps 1-2, 11. */
   288     AutoCycleDetector detect(cx, obj);
   289     if (!detect.init())
   290         return false;
   291     if (detect.foundCycle()) {
   292         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CYCLIC_VALUE, js_object_str);
   293         return false;
   294     }
   296     if (!scx->sb.append('{'))
   297         return false;
   299     /* Steps 5-7. */
   300     Maybe<AutoIdVector> ids;
   301     const AutoIdVector *props;
   302     if (scx->replacer && !scx->replacer->isCallable()) {
   303         JS_ASSERT(JS_IsArrayObject(cx, scx->replacer));
   304         props = &scx->propertyList;
   305     } else {
   306         JS_ASSERT_IF(scx->replacer, scx->propertyList.length() == 0);
   307         ids.construct(cx);
   308         if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, ids.addr()))
   309             return false;
   310         props = ids.addr();
   311     }
   313     /* My kingdom for not-quite-initialized-from-the-start references. */
   314     const AutoIdVector &propertyList = *props;
   316     /* Steps 8-10, 13. */
   317     bool wroteMember = false;
   318     RootedId id(cx);
   319     for (size_t i = 0, len = propertyList.length(); i < len; i++) {
   320         /*
   321          * Steps 8a-8b.  Note that the call to Str is broken up into 1) getting
   322          * the property; 2) processing for toJSON, calling the replacer, and
   323          * handling boxed Number/String/Boolean objects; 3) filtering out
   324          * values which process to |undefined|, and 4) stringifying all values
   325          * which pass the filter.
   326          */
   327         id = propertyList[i];
   328         RootedValue outputValue(cx);
   329         if (!JSObject::getGeneric(cx, obj, obj, id, &outputValue))
   330             return false;
   331         if (!PreprocessValue(cx, obj, HandleId(id), &outputValue, scx))
   332             return false;
   333         if (IsFilteredValue(outputValue))
   334             continue;
   336         /* Output a comma unless this is the first member to write. */
   337         if (wroteMember && !scx->sb.append(','))
   338             return false;
   339         wroteMember = true;
   341         if (!WriteIndent(cx, scx, scx->depth))
   342             return false;
   344         JSString *s = IdToString(cx, id);
   345         if (!s)
   346             return false;
   348         if (!Quote(cx, scx->sb, s) ||
   349             !scx->sb.append(':') ||
   350             !(scx->gap.empty() || scx->sb.append(' ')) ||
   351             !Str(cx, outputValue, scx))
   352         {
   353             return false;
   354         }
   355     }
   357     if (wroteMember && !WriteIndent(cx, scx, scx->depth - 1))
   358         return false;
   360     return scx->sb.append('}');
   361 }
   363 /* ES5 15.12.3 JA. */
   364 static bool
   365 JA(JSContext *cx, HandleObject obj, StringifyContext *scx)
   366 {
   367     /*
   368      * This method implements the JA algorithm in ES5 15.12.3, but:
   369      *
   370      *   * The algorithm is somewhat reformulated to allow the final string to
   371      *     be streamed into a single buffer, rather than be created and copied
   372      *     into place incrementally as the ES5 algorithm specifies it.  This
   373      *     requires moving portions of the Str call in 8a into this algorithm
   374      *     (and in JO as well).
   375      */
   377     /* Steps 1-2, 11. */
   378     AutoCycleDetector detect(cx, obj);
   379     if (!detect.init())
   380         return false;
   381     if (detect.foundCycle()) {
   382         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CYCLIC_VALUE, js_object_str);
   383         return false;
   384     }
   386     if (!scx->sb.append('['))
   387         return false;
   389     /* Step 6. */
   390     uint32_t length;
   391     if (!GetLengthProperty(cx, obj, &length))
   392         return false;
   394     /* Steps 7-10. */
   395     if (length != 0) {
   396         /* Steps 4, 10b(i). */
   397         if (!WriteIndent(cx, scx, scx->depth))
   398             return false;
   400         /* Steps 7-10. */
   401         RootedValue outputValue(cx);
   402         for (uint32_t i = 0; i < length; i++) {
   403             /*
   404              * Steps 8a-8c.  Again note how the call to the spec's Str method
   405              * is broken up into getting the property, running it past toJSON
   406              * and the replacer and maybe unboxing, and interpreting some
   407              * values as |null| in separate steps.
   408              */
   409             if (!JSObject::getElement(cx, obj, obj, i, &outputValue))
   410                 return false;
   411             if (!PreprocessValue(cx, obj, i, &outputValue, scx))
   412                 return false;
   413             if (IsFilteredValue(outputValue)) {
   414                 if (!scx->sb.append("null"))
   415                     return false;
   416             } else {
   417                 if (!Str(cx, outputValue, scx))
   418                     return false;
   419             }
   421             /* Steps 3, 4, 10b(i). */
   422             if (i < length - 1) {
   423                 if (!scx->sb.append(','))
   424                     return false;
   425                 if (!WriteIndent(cx, scx, scx->depth))
   426                     return false;
   427             }
   428         }
   430         /* Step 10(b)(iii). */
   431         if (!WriteIndent(cx, scx, scx->depth - 1))
   432             return false;
   433     }
   435     return scx->sb.append(']');
   436 }
   438 static bool
   439 Str(JSContext *cx, const Value &v, StringifyContext *scx)
   440 {
   441     /* Step 11 must be handled by the caller. */
   442     JS_ASSERT(!IsFilteredValue(v));
   444     JS_CHECK_RECURSION(cx, return false);
   446     /*
   447      * This method implements the Str algorithm in ES5 15.12.3, but:
   448      *
   449      *   * We move property retrieval (step 1) into callers to stream the
   450      *     stringification process and avoid constantly copying strings.
   451      *   * We move the preprocessing in steps 2-4 into a helper function to
   452      *     allow both JO and JA to use this method.  While JA could use it
   453      *     without this move, JO must omit any |undefined|-valued property per
   454      *     so it can't stream out a value using the Str method exactly as
   455      *     defined by ES5.
   456      *   * We move step 11 into callers, again to ease streaming.
   457      */
   459     /* Step 8. */
   460     if (v.isString())
   461         return Quote(cx, scx->sb, v.toString());
   463     /* Step 5. */
   464     if (v.isNull())
   465         return scx->sb.append("null");
   467     /* Steps 6-7. */
   468     if (v.isBoolean())
   469         return v.toBoolean() ? scx->sb.append("true") : scx->sb.append("false");
   471     /* Step 9. */
   472     if (v.isNumber()) {
   473         if (v.isDouble()) {
   474             if (!IsFinite(v.toDouble()))
   475                 return scx->sb.append("null");
   476         }
   478         StringBuffer sb(cx);
   479         if (!NumberValueToStringBuffer(cx, v, sb))
   480             return false;
   482         return scx->sb.append(sb.begin(), sb.length());
   483     }
   485     /* Step 10. */
   486     JS_ASSERT(v.isObject());
   487     RootedObject obj(cx, &v.toObject());
   489     scx->depth++;
   490     bool ok;
   491     if (ObjectClassIs(obj, ESClass_Array, cx))
   492         ok = JA(cx, obj, scx);
   493     else
   494         ok = JO(cx, obj, scx);
   495     scx->depth--;
   497     return ok;
   498 }
   500 /* ES5 15.12.3. */
   501 bool
   502 js_Stringify(JSContext *cx, MutableHandleValue vp, JSObject *replacer_, Value space_,
   503              StringBuffer &sb)
   504 {
   505     RootedObject replacer(cx, replacer_);
   506     RootedValue space(cx, space_);
   508     /* Step 4. */
   509     AutoIdVector propertyList(cx);
   510     if (replacer) {
   511         if (replacer->isCallable()) {
   512             /* Step 4a(i): use replacer to transform values.  */
   513         } else if (ObjectClassIs(replacer, ESClass_Array, cx)) {
   514             /*
   515              * Step 4b: The spec algorithm is unhelpfully vague about the exact
   516              * steps taken when the replacer is an array, regarding the exact
   517              * sequence of [[Get]] calls for the array's elements, when its
   518              * overall length is calculated, whether own or own plus inherited
   519              * properties are considered, and so on.  A rewrite was proposed in
   520              * <https://mail.mozilla.org/pipermail/es5-discuss/2011-April/003976.html>,
   521              * whose steps are copied below, and which are implemented here.
   522              *
   523              * i.   Let PropertyList be an empty internal List.
   524              * ii.  Let len be the result of calling the [[Get]] internal
   525              *      method of replacer with the argument "length".
   526              * iii. Let i be 0.
   527              * iv.  While i < len:
   528              *      1. Let item be undefined.
   529              *      2. Let v be the result of calling the [[Get]] internal
   530              *         method of replacer with the argument ToString(i).
   531              *      3. If Type(v) is String then let item be v.
   532              *      4. Else if Type(v) is Number then let item be ToString(v).
   533              *      5. Else if Type(v) is Object then
   534              *         a. If the [[Class]] internal property of v is "String"
   535              *            or "Number" then let item be ToString(v).
   536              *      6. If item is not undefined and item is not currently an
   537              *         element of PropertyList then,
   538              *         a. Append item to the end of PropertyList.
   539              *      7. Let i be i + 1.
   540              */
   542             /* Step 4b(ii). */
   543             uint32_t len;
   544             JS_ALWAYS_TRUE(GetLengthProperty(cx, replacer, &len));
   545             if (replacer->is<ArrayObject>() && !replacer->isIndexed())
   546                 len = Min(len, replacer->getDenseInitializedLength());
   548             // Cap the initial size to a moderately small value.  This avoids
   549             // ridiculous over-allocation if an array with bogusly-huge length
   550             // is passed in.  If we end up having to add elements past this
   551             // size, the set will naturally resize to accommodate them.
   552             const uint32_t MaxInitialSize = 1024;
   553             HashSet<jsid, JsidHasher> idSet(cx);
   554             if (!idSet.init(Min(len, MaxInitialSize)))
   555                 return false;
   557             /* Step 4b(iii). */
   558             uint32_t i = 0;
   560             /* Step 4b(iv). */
   561             RootedValue v(cx);
   562             for (; i < len; i++) {
   563                 if (!CheckForInterrupt(cx))
   564                     return false;
   566                 /* Step 4b(iv)(2). */
   567                 if (!JSObject::getElement(cx, replacer, replacer, i, &v))
   568                     return false;
   570                 RootedId id(cx);
   571                 if (v.isNumber()) {
   572                     /* Step 4b(iv)(4). */
   573                     int32_t n;
   574                     if (v.isNumber() && ValueFitsInInt32(v, &n) && INT_FITS_IN_JSID(n)) {
   575                         id = INT_TO_JSID(n);
   576                     } else {
   577                         if (!ValueToId<CanGC>(cx, v, &id))
   578                             return false;
   579                     }
   580                 } else if (v.isString() ||
   581                            IsObjectWithClass(v, ESClass_String, cx) ||
   582                            IsObjectWithClass(v, ESClass_Number, cx))
   583                 {
   584                     /* Step 4b(iv)(3), 4b(iv)(5). */
   585                     if (!ValueToId<CanGC>(cx, v, &id))
   586                         return false;
   587                 } else {
   588                     continue;
   589                 }
   591                 /* Step 4b(iv)(6). */
   592                 HashSet<jsid, JsidHasher>::AddPtr p = idSet.lookupForAdd(id);
   593                 if (!p) {
   594                     /* Step 4b(iv)(6)(a). */
   595                     if (!idSet.add(p, id) || !propertyList.append(id))
   596                         return false;
   597                 }
   598             }
   599         } else {
   600             replacer = nullptr;
   601         }
   602     }
   604     /* Step 5. */
   605     if (space.isObject()) {
   606         RootedObject spaceObj(cx, &space.toObject());
   607         if (ObjectClassIs(spaceObj, ESClass_Number, cx)) {
   608             double d;
   609             if (!ToNumber(cx, space, &d))
   610                 return false;
   611             space = NumberValue(d);
   612         } else if (ObjectClassIs(spaceObj, ESClass_String, cx)) {
   613             JSString *str = ToStringSlow<CanGC>(cx, space);
   614             if (!str)
   615                 return false;
   616             space = StringValue(str);
   617         }
   618     }
   620     StringBuffer gap(cx);
   622     if (space.isNumber()) {
   623         /* Step 6. */
   624         double d;
   625         JS_ALWAYS_TRUE(ToInteger(cx, space, &d));
   626         d = Min(10.0, d);
   627         if (d >= 1 && !gap.appendN(' ', uint32_t(d)))
   628             return false;
   629     } else if (space.isString()) {
   630         /* Step 7. */
   631         JSLinearString *str = space.toString()->ensureLinear(cx);
   632         if (!str)
   633             return false;
   634         JS::Anchor<JSString *> anchor(str);
   635         size_t len = Min(size_t(10), space.toString()->length());
   636         if (!gap.append(str->chars(), len))
   637             return false;
   638     } else {
   639         /* Step 8. */
   640         JS_ASSERT(gap.empty());
   641     }
   643     /* Step 9. */
   644     RootedObject wrapper(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
   645     if (!wrapper)
   646         return false;
   648     /* Step 10. */
   649     RootedId emptyId(cx, NameToId(cx->names().empty));
   650     if (!DefineNativeProperty(cx, wrapper, emptyId, vp, JS_PropertyStub, JS_StrictPropertyStub,
   651                               JSPROP_ENUMERATE))
   652     {
   653         return false;
   654     }
   656     /* Step 11. */
   657     StringifyContext scx(cx, sb, gap, replacer, propertyList);
   658     if (!PreprocessValue(cx, wrapper, HandleId(emptyId), vp, &scx))
   659         return false;
   660     if (IsFilteredValue(vp))
   661         return true;
   663     return Str(cx, vp, &scx);
   664 }
   666 /* ES5 15.12.2 Walk. */
   667 static bool
   668 Walk(JSContext *cx, HandleObject holder, HandleId name, HandleValue reviver, MutableHandleValue vp)
   669 {
   670     JS_CHECK_RECURSION(cx, return false);
   672     /* Step 1. */
   673     RootedValue val(cx);
   674     if (!JSObject::getGeneric(cx, holder, holder, name, &val))
   675         return false;
   677     /* Step 2. */
   678     if (val.isObject()) {
   679         RootedObject obj(cx, &val.toObject());
   681         if (ObjectClassIs(obj, ESClass_Array, cx)) {
   682             /* Step 2a(ii). */
   683             uint32_t length;
   684             if (!GetLengthProperty(cx, obj, &length))
   685                 return false;
   687             /* Step 2a(i), 2a(iii-iv). */
   688             RootedId id(cx);
   689             RootedValue newElement(cx);
   690             for (uint32_t i = 0; i < length; i++) {
   691                 if (!IndexToId(cx, i, &id))
   692                     return false;
   694                 /* Step 2a(iii)(1). */
   695                 if (!Walk(cx, obj, id, reviver, &newElement))
   696                     return false;
   698                 if (newElement.isUndefined()) {
   699                     /* Step 2a(iii)(2). */
   700                     bool succeeded;
   701                     if (!JSObject::deleteByValue(cx, obj, IdToValue(id), &succeeded))
   702                         return false;
   703                 } else {
   704                     /* Step 2a(iii)(3). */
   705                     // XXX This definition should ignore success/failure, when
   706                     //     our property-definition APIs indicate that.
   707                     if (!JSObject::defineGeneric(cx, obj, id, newElement))
   708                         return false;
   709                 }
   710             }
   711         } else {
   712             /* Step 2b(i). */
   713             AutoIdVector keys(cx);
   714             if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, &keys))
   715                 return false;
   717             /* Step 2b(ii). */
   718             RootedId id(cx);
   719             RootedValue newElement(cx);
   720             for (size_t i = 0, len = keys.length(); i < len; i++) {
   721                 /* Step 2b(ii)(1). */
   722                 id = keys[i];
   723                 if (!Walk(cx, obj, id, reviver, &newElement))
   724                     return false;
   726                 if (newElement.isUndefined()) {
   727                     /* Step 2b(ii)(2). */
   728                     bool succeeded;
   729                     if (!JSObject::deleteByValue(cx, obj, IdToValue(id), &succeeded))
   730                         return false;
   731                 } else {
   732                     /* Step 2b(ii)(3). */
   733                     // XXX This definition should ignore success/failure, when
   734                     //     our property-definition APIs indicate that.
   735                     if (!JSObject::defineGeneric(cx, obj, id, newElement))
   736                         return false;
   737                 }
   738             }
   739         }
   740     }
   742     /* Step 3. */
   743     RootedString key(cx, IdToString(cx, name));
   744     if (!key)
   745         return false;
   747     InvokeArgs args(cx);
   748     if (!args.init(2))
   749         return false;
   751     args.setCallee(reviver);
   752     args.setThis(ObjectValue(*holder));
   753     args[0].setString(key);
   754     args[1].set(val);
   756     if (!Invoke(cx, args))
   757         return false;
   758     vp.set(args.rval());
   759     return true;
   760 }
   762 static bool
   763 Revive(JSContext *cx, HandleValue reviver, MutableHandleValue vp)
   764 {
   765     RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
   766     if (!obj)
   767         return false;
   769     if (!JSObject::defineProperty(cx, obj, cx->names().empty, vp))
   770         return false;
   772     Rooted<jsid> id(cx, NameToId(cx->names().empty));
   773     return Walk(cx, obj, id, reviver, vp);
   774 }
   776 bool
   777 js::ParseJSONWithReviver(JSContext *cx, ConstTwoByteChars chars, size_t length,
   778                          HandleValue reviver, MutableHandleValue vp)
   779 {
   780     /* 15.12.2 steps 2-3. */
   781     JSONParser parser(cx, chars, length);
   782     if (!parser.parse(vp))
   783         return false;
   785     /* 15.12.2 steps 4-5. */
   786     if (js_IsCallable(reviver))
   787         return Revive(cx, reviver, vp);
   788     return true;
   789 }
   791 #if JS_HAS_TOSOURCE
   792 static bool
   793 json_toSource(JSContext *cx, unsigned argc, Value *vp)
   794 {
   795     CallArgs args = CallArgsFromVp(argc, vp);
   796     args.rval().setString(cx->names().JSON);
   797     return true;
   798 }
   799 #endif
   801 /* ES5 15.12.2. */
   802 static bool
   803 json_parse(JSContext *cx, unsigned argc, Value *vp)
   804 {
   805     CallArgs args = CallArgsFromVp(argc, vp);
   807     /* Step 1. */
   808     JSString *str = (args.length() >= 1)
   809                     ? ToString<CanGC>(cx, args[0])
   810                     : cx->names().undefined;
   811     if (!str)
   812         return false;
   814     Rooted<JSFlatString*> flat(cx, str->ensureFlat(cx));
   815     if (!flat)
   816         return false;
   818     JS::Anchor<JSString *> anchor(flat);
   820     RootedValue reviver(cx, args.get(1));
   822     /* Steps 2-5. */
   823     return ParseJSONWithReviver(cx, ConstTwoByteChars(flat->chars(), flat->length()),
   824                                 flat->length(), reviver, args.rval());
   825 }
   827 /* ES5 15.12.3. */
   828 bool
   829 json_stringify(JSContext *cx, unsigned argc, Value *vp)
   830 {
   831     CallArgs args = CallArgsFromVp(argc, vp);
   832     RootedObject replacer(cx, args.get(1).isObject() ? &args[1].toObject() : nullptr);
   833     RootedValue value(cx, args.get(0));
   834     RootedValue space(cx, args.get(2));
   836     StringBuffer sb(cx);
   837     if (!js_Stringify(cx, &value, replacer, space, sb))
   838         return false;
   840     // XXX This can never happen to nsJSON.cpp, but the JSON object
   841     // needs to support returning undefined. So this is a little awkward
   842     // for the API, because we want to support streaming writers.
   843     if (!sb.empty()) {
   844         JSString *str = sb.finishString();
   845         if (!str)
   846             return false;
   847         args.rval().setString(str);
   848     } else {
   849         args.rval().setUndefined();
   850     }
   852     return true;
   853 }
   855 static const JSFunctionSpec json_static_methods[] = {
   856 #if JS_HAS_TOSOURCE
   857     JS_FN(js_toSource_str,  json_toSource,      0, 0),
   858 #endif
   859     JS_FN("parse",          json_parse,         2, 0),
   860     JS_FN("stringify",      json_stringify,     3, 0),
   861     JS_FS_END
   862 };
   864 JSObject *
   865 js_InitJSONClass(JSContext *cx, HandleObject obj)
   866 {
   867     Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
   869     /*
   870      * JSON requires that Boolean.prototype.valueOf be created and stashed in a
   871      * reserved slot on the global object; see js::BooleanGetPrimitiveValueSlow
   872      * called from PreprocessValue above.
   873      */
   874     if (!GlobalObject::getOrCreateBooleanPrototype(cx, global))
   875         return nullptr;
   877     RootedObject proto(cx, obj->as<GlobalObject>().getOrCreateObjectPrototype(cx));
   878     RootedObject JSON(cx, NewObjectWithClassProto(cx, &JSONClass, proto, global, SingletonObject));
   879     if (!JSON)
   880         return nullptr;
   882     if (!JS_DefineProperty(cx, global, js_JSON_str, JSON, 0,
   883                            JS_PropertyStub, JS_StrictPropertyStub))
   884         return nullptr;
   886     if (!JS_DefineFunctions(cx, JSON, json_static_methods))
   887         return nullptr;
   889     global->setConstructor(JSProto_JSON, ObjectValue(*JSON));
   891     return JSON;
   892 }

mercurial