LCOV - code coverage report
Current view: directory - js/src - json.cpp (source / functions) Found Hit Coverage
Test: app.info Lines: 390 288 73.8 %
Date: 2012-06-02 Functions: 25 24 96.0 %

       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                 : 
      68                 : #include "vm/Stack-inl.h"
      69                 : #include "vm/StringBuffer-inl.h"
      70                 : 
      71                 : using namespace js;
      72                 : using namespace js::gc;
      73                 : using namespace js::types;
      74                 : 
      75                 : Class 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. */
      88                 : JSBool
      89            8177 : js_json_parse(JSContext *cx, unsigned argc, Value *vp)
      90                 : {
      91                 :     /* Step 1. */
      92                 :     JSLinearString *linear;
      93            8177 :     if (argc >= 1) {
      94            7997 :         JSString *str = ToString(cx, vp[2]);
      95            7997 :         if (!str)
      96               0 :             return false;
      97            7997 :         linear = str->ensureLinear(cx);
      98            7997 :         if (!linear)
      99               0 :             return false;
     100                 :     } else {
     101             180 :         linear = cx->runtime->atomState.typeAtoms[JSTYPE_VOID];
     102                 :     }
     103           16354 :     JS::Anchor<JSString *> anchor(linear);
     104                 : 
     105            8177 :     Value reviver = (argc >= 2) ? vp[3] : UndefinedValue();
     106                 : 
     107                 :     /* Steps 2-5. */
     108            8177 :     return ParseJSONWithReviver(cx, linear->chars(), linear->length(), reviver, vp);
     109                 : }
     110                 : 
     111                 : /* ES5 15.12.3. */
     112                 : JSBool
     113           16298 : js_json_stringify(JSContext *cx, unsigned argc, Value *vp)
     114                 : {
     115           16298 :     *vp = (argc >= 1) ? vp[2] : UndefinedValue();
     116            2644 :     JSObject *replacer = (argc >= 2 && vp[3].isObject())
     117            1860 :                          ? &vp[3].toObject()
     118           20802 :                          : NULL;
     119           16298 :     Value space = (argc >= 3) ? vp[4] : UndefinedValue();
     120                 : 
     121           32596 :     StringBuffer sb(cx);
     122           16298 :     if (!js_Stringify(cx, vp, replacer, space, sb))
     123               0 :         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           16298 :     if (!sb.empty()) {
     129           16281 :         JSString *str = sb.finishString();
     130           16281 :         if (!str)
     131               0 :             return false;
     132           16281 :         vp->setString(str);
     133                 :     } else {
     134              17 :         vp->setUndefined();
     135                 :     }
     136                 : 
     137           16298 :     return true;
     138                 : }
     139                 : 
     140         8726255 : static 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         8726255 :     return c == '"' || c == '\\' || c < ' ';
     148                 : }
     149                 : 
     150                 : /* ES5 15.12.3 Quote. */
     151                 : static bool
     152          188022 : Quote(JSContext *cx, StringBuffer &sb, JSString *str)
     153                 : {
     154          376044 :     JS::Anchor<JSString *> anchor(str);
     155          188022 :     size_t len = str->length();
     156          188022 :     const jschar *buf = str->getChars(cx);
     157          188022 :     if (!buf)
     158               0 :         return false;
     159                 : 
     160                 :     /* Step 1. */
     161          188022 :     if (!sb.append('"'))
     162               0 :         return false;
     163                 : 
     164                 :     /* Step 2. */
     165          354224 :     for (size_t i = 0; i < len; ++i) {
     166                 :         /* Batch-append maximal character sequences containing no escapes. */
     167          354035 :         size_t mark = i;
     168         8560053 :         do {
     169         8726255 :             if (IsQuoteSpecialCharacter(buf[i]))
     170          166202 :                 break;
     171                 :         } while (++i < len);
     172          354035 :         if (i > mark) {
     173          303745 :             if (!sb.append(&buf[mark], i - mark))
     174               0 :                 return false;
     175          303745 :             if (i == len)
     176          187833 :                 break;
     177                 :         }
     178                 : 
     179          166202 :         jschar c = buf[i];
     180          166202 :         if (c == '"' || c == '\\') {
     181          331104 :             if (!sb.append('\\') || !sb.append(c))
     182               0 :                 return false;
     183             650 :         } 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               1 :                          : 't';
     193               1 :            if (!sb.append('\\') || !sb.append(abbrev))
     194               0 :                return false;
     195                 :         } else {
     196             649 :             JS_ASSERT(c < ' ');
     197             649 :             if (!sb.append("\\u00"))
     198               0 :                 return false;
     199             649 :             JS_ASSERT((c >> 4) < 10);
     200             649 :             uint8_t x = c >> 4, y = c % 16;
     201             649 :             if (!sb.append('0' + x) || !sb.append(y < 10 ? '0' + y : 'a' + (y - 10)))
     202               0 :                 return false;
     203                 :         }
     204                 :     }
     205                 : 
     206                 :     /* Steps 3-4. */
     207          188022 :     return sb.append('"');
     208                 : }
     209                 : 
     210                 : class StringifyContext
     211                 : {
     212                 :   public:
     213           16866 :     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           16866 :         objectStack(cx)
     221           16866 :     {}
     222                 : 
     223           16866 :     bool init() {
     224           16866 :         return objectStack.init(16);
     225                 :     }
     226                 : 
     227                 : #ifdef DEBUG
     228           16866 :     ~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                 : 
     239                 : static JSBool Str(JSContext *cx, const Value &v, StringifyContext *scx);
     240                 : 
     241                 : static JSBool
     242          167267 : WriteIndent(JSContext *cx, StringifyContext *scx, uint32_t limit)
     243                 : {
     244          167267 :     if (!scx->gap.empty()) {
     245            6291 :         if (!scx->sb.append('\n'))
     246               0 :             return JS_FALSE;
     247           17566 :         for (uint32_t i = 0; i < limit; i++) {
     248           11275 :             if (!scx->sb.append(scx->gap.begin(), scx->gap.end()))
     249               0 :                 return JS_FALSE;
     250                 :         }
     251                 :     }
     252                 : 
     253          167267 :     return JS_TRUE;
     254                 : }
     255                 : 
     256                 : class CycleDetector
     257                 : {
     258                 :   public:
     259           39350 :     CycleDetector(StringifyContext *scx, JSObject *obj)
     260           39350 :       : objectStack(scx->objectStack), obj(obj) {
     261           39350 :     }
     262                 : 
     263           39350 :     bool init(JSContext *cx) {
     264           39350 :         HashSet<JSObject *>::AddPtr ptr = objectStack.lookupForAdd(obj);
     265           39350 :         if (ptr) {
     266               0 :             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CYCLIC_VALUE, js_object_str);
     267               0 :             return false;
     268                 :         }
     269           39350 :         return objectStack.add(ptr, obj);
     270                 :     }
     271                 : 
     272           39350 :     ~CycleDetector() {
     273           39350 :         objectStack.remove(obj);
     274           39350 :     }
     275                 : 
     276                 :   private:
     277                 :     HashSet<JSObject *> &objectStack;
     278                 :     JSObject *const obj;
     279                 : };
     280                 : 
     281                 : template<typename KeyType>
     282                 : class KeyStringifier {
     283                 : };
     284                 : 
     285                 : template<>
     286                 : class KeyStringifier<uint32_t> {
     287                 :   public:
     288             616 :     static JSString *toString(JSContext *cx, uint32_t index) {
     289             616 :         return IndexToString(cx, index);
     290                 :     }
     291                 : };
     292                 : 
     293                 : template<>
     294                 : class KeyStringifier<jsid> {
     295                 :   public:
     296             500 :     static JSString *toString(JSContext *cx, jsid id) {
     297             500 :         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                 :  */
     305                 : template<typename KeyType>
     306                 : static bool
     307          150176 : PreprocessValue(JSContext *cx, JSObject *holder, KeyType key, Value *vp, StringifyContext *scx)
     308                 : {
     309          150176 :     JSString *keyStr = NULL;
     310                 : 
     311                 :     /* Step 2. */
     312          150176 :     if (vp->isObject()) {
     313                 :         Value toJSON;
     314           39402 :         jsid id = ATOM_TO_JSID(cx->runtime->atomState.toJSONAtom);
     315           39402 :         if (!js_GetMethod(cx, &vp->toObject(), id, JSGET_NO_METHOD_BARRIER, &toJSON))
     316               0 :             return false;
     317                 : 
     318           39402 :         if (js_IsCallable(toJSON)) {
     319            1116 :             keyStr = KeyStringifier<KeyType>::toString(cx, key);
     320            1116 :             if (!keyStr)
     321               0 :                 return false;
     322                 : 
     323            2232 :             InvokeArgsGuard args;
     324            1116 :             if (!cx->stack.pushInvokeArgs(cx, 1, &args))
     325               0 :                 return false;
     326                 : 
     327            1116 :             args.calleev() = toJSON;
     328            1116 :             args.thisv() = *vp;
     329            1116 :             args[0] = StringValue(keyStr);
     330                 : 
     331            1116 :             if (!Invoke(cx, args))
     332               0 :                 return false;
     333            2232 :             *vp = args.rval();
     334                 :         }
     335                 :     }
     336                 : 
     337                 :     /* Step 3. */
     338          150176 :     if (scx->replacer && scx->replacer->isCallable()) {
     339               0 :         if (!keyStr) {
     340               0 :             keyStr = KeyStringifier<KeyType>::toString(cx, key);
     341               0 :             if (!keyStr)
     342               0 :                 return false;
     343                 :         }
     344                 : 
     345               0 :         InvokeArgsGuard args;
     346               0 :         if (!cx->stack.pushInvokeArgs(cx, 2, &args))
     347               0 :             return false;
     348                 : 
     349               0 :         args.calleev() = ObjectValue(*scx->replacer);
     350               0 :         args.thisv() = ObjectValue(*holder);
     351               0 :         args[0] = StringValue(keyStr);
     352               0 :         args[1] = *vp;
     353                 : 
     354               0 :         if (!Invoke(cx, args))
     355               0 :             return false;
     356               0 :         *vp = args.rval();
     357                 :     }
     358                 : 
     359                 :     /* Step 4. */
     360          150176 :     if (vp->isObject()) {
     361           39402 :         JSObject &obj = vp->toObject();
     362           39402 :         if (ObjectClassIs(obj, ESClass_Number, cx)) {
     363                 :             double d;
     364              12 :             if (!ToNumber(cx, *vp, &d))
     365               0 :                 return false;
     366              12 :             vp->setNumber(d);
     367           39390 :         } else if (ObjectClassIs(obj, ESClass_String, cx)) {
     368               9 :             JSString *str = ToStringSlow(cx, *vp);
     369               9 :             if (!str)
     370               0 :                 return false;
     371               9 :             vp->setString(str);
     372           39381 :         } else if (ObjectClassIs(obj, ESClass_Boolean, cx)) {
     373               9 :             if (!BooleanGetPrimitiveValue(cx, obj, vp))
     374               0 :                 return false;
     375               9 :             JS_ASSERT(vp->isBoolean());
     376                 :         }
     377                 :     }
     378                 : 
     379          150176 :     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                 :  */
     389                 : static inline bool
     390          300056 : IsFilteredValue(const Value &v)
     391                 : {
     392          300056 :     return v.isUndefined() || js_IsCallable(v) || (v.isObject() && v.toObject().isXML());
     393                 : }
     394                 : 
     395                 : /* ES5 15.12.3 JO. */
     396                 : static JSBool
     397           29752 : JO(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           59504 :     CycleDetector detect(scx, obj);
     411           29752 :     if (!detect.init(cx))
     412               0 :         return JS_FALSE;
     413                 : 
     414           29752 :     if (!scx->sb.append('{'))
     415               0 :         return JS_FALSE;
     416                 : 
     417                 :     /* Steps 5-7. */
     418           59504 :     Maybe<AutoIdVector> ids;
     419                 :     const AutoIdVector *props;
     420           29752 :     if (scx->replacer && !scx->replacer->isCallable()) {
     421            1860 :         JS_ASSERT(JS_IsArrayObject(cx, scx->replacer));
     422            1860 :         props = &scx->propertyList;
     423                 :     } else {
     424           27892 :         JS_ASSERT_IF(scx->replacer, scx->propertyList.length() == 0);
     425           27892 :         ids.construct(cx);
     426           27892 :         if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, ids.addr()))
     427               0 :             return false;
     428           27892 :         props = ids.addr();
     429                 :     }
     430                 : 
     431                 :     /* My kingdom for not-quite-initialized-from-the-start references. */
     432           29752 :     const AutoIdVector &propertyList = *props;
     433                 : 
     434                 :     /* Steps 8-10, 13. */
     435           29752 :     bool wroteMember = false;
     436          145177 :     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          115425 :         const jsid &id = propertyList[i];
     445                 :         Value outputValue;
     446          115425 :         if (!obj->getGeneric(cx, id, &outputValue))
     447               0 :             return false;
     448          115425 :         if (!PreprocessValue(cx, obj, id, &outputValue, scx))
     449               0 :             return false;
     450          115425 :         if (IsFilteredValue(outputValue))
     451             269 :             continue;
     452                 : 
     453                 :         /* Output a comma unless this is the first member to write. */
     454          115156 :         if (wroteMember && !scx->sb.append(','))
     455               0 :             return false;
     456          115156 :         wroteMember = true;
     457                 : 
     458          115156 :         if (!WriteIndent(cx, scx, scx->depth))
     459               0 :             return false;
     460                 : 
     461          115156 :         JSString *s = IdToString(cx, id);
     462          115156 :         if (!s)
     463               0 :             return false;
     464                 : 
     465          460624 :         if (!Quote(cx, scx->sb, s) ||
     466          115156 :             !scx->sb.append(':') ||
     467          119646 :             !(scx->gap.empty() || scx->sb.append(' ')) ||
     468          115156 :             !Str(cx, outputValue, scx))
     469                 :         {
     470               0 :             return false;
     471                 :         }
     472                 :     }
     473                 : 
     474           29752 :     if (wroteMember && !WriteIndent(cx, scx, scx->depth - 1))
     475               0 :         return false;
     476                 : 
     477           29752 :     return scx->sb.append('}');
     478                 : }
     479                 : 
     480                 : /* ES5 15.12.3 JA. */
     481                 : static JSBool
     482            9598 : JA(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           19196 :     CycleDetector detect(scx, obj);
     496            9598 :     if (!detect.init(cx))
     497               0 :         return JS_FALSE;
     498                 : 
     499            9598 :     if (!scx->sb.append('['))
     500               0 :         return JS_FALSE;
     501                 : 
     502                 :     /* Step 6. */
     503                 :     uint32_t length;
     504            9598 :     if (!js_GetLengthProperty(cx, obj, &length))
     505               0 :         return JS_FALSE;
     506                 : 
     507                 :     /* Steps 7-10. */
     508            9598 :     if (length != 0) {
     509                 :         /* Steps 4, 10b(i). */
     510            6529 :         if (!WriteIndent(cx, scx, scx->depth))
     511               0 :             return JS_FALSE;
     512                 : 
     513                 :         /* Steps 7-10. */
     514                 :         Value outputValue;
     515           24414 :         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           17885 :             if (!obj->getElement(cx, i, &outputValue))
     523               0 :                 return JS_FALSE;
     524           17885 :             if (!PreprocessValue(cx, obj, i, &outputValue, scx))
     525               0 :                 return JS_FALSE;
     526           17885 :             if (IsFilteredValue(outputValue)) {
     527              10 :                 if (!scx->sb.append("null"))
     528               0 :                     return JS_FALSE;
     529                 :             } else {
     530           17875 :                 if (!Str(cx, outputValue, scx))
     531               0 :                     return JS_FALSE;
     532                 :             }
     533                 : 
     534                 :             /* Steps 3, 4, 10b(i). */
     535           17885 :             if (i < length - 1) {
     536           11356 :                 if (!scx->sb.append(','))
     537               0 :                     return JS_FALSE;
     538           11356 :                 if (!WriteIndent(cx, scx, scx->depth))
     539               0 :                     return JS_FALSE;
     540                 :             }
     541                 :         }
     542                 : 
     543                 :         /* Step 10(b)(iii). */
     544            6529 :         if (!WriteIndent(cx, scx, scx->depth - 1))
     545               0 :             return JS_FALSE;
     546                 :     }
     547                 : 
     548            9598 :     return scx->sb.append(']');
     549                 : }
     550                 : 
     551                 : static JSBool
     552          149880 : Str(JSContext *cx, const Value &v, StringifyContext *scx)
     553                 : {
     554                 :     /* Step 11 must be handled by the caller. */
     555          149880 :     JS_ASSERT(!IsFilteredValue(v));
     556                 : 
     557          149880 :     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          149880 :     if (v.isString())
     574           72866 :         return Quote(cx, scx->sb, v.toString());
     575                 : 
     576                 :     /* Step 5. */
     577           77014 :     if (v.isNull())
     578            4700 :         return scx->sb.append("null");
     579                 : 
     580                 :     /* Steps 6-7. */
     581           72314 :     if (v.isBoolean())
     582            6149 :         return v.toBoolean() ? scx->sb.append("true") : scx->sb.append("false");
     583                 : 
     584                 :     /* Step 9. */
     585           66165 :     if (v.isNumber()) {
     586           26815 :         if (v.isDouble()) {
     587           19148 :             if (!JSDOUBLE_IS_FINITE(v.toDouble()))
     588               3 :                 return scx->sb.append("null");
     589                 :         }
     590                 : 
     591           53624 :         StringBuffer sb(cx);
     592           26812 :         if (!NumberValueToStringBuffer(cx, v, sb))
     593               0 :             return false;
     594                 : 
     595           26812 :         return scx->sb.append(sb.begin(), sb.length());
     596                 :     }
     597                 : 
     598                 :     /* Step 10. */
     599           39350 :     JS_ASSERT(v.isObject());
     600           39350 :     JSObject *obj = &v.toObject();
     601                 : 
     602           39350 :     scx->depth++;
     603                 :     JSBool ok;
     604           39350 :     if (ObjectClassIs(v.toObject(), ESClass_Array, cx))
     605            9598 :         ok = JA(cx, obj, scx);
     606                 :     else
     607           29752 :         ok = JO(cx, obj, scx);
     608           39350 :     scx->depth--;
     609                 : 
     610           39350 :     return ok;
     611                 : }
     612                 : 
     613                 : /* ES5 15.12.3. */
     614                 : JSBool
     615           16866 : js_Stringify(JSContext *cx, Value *vp, JSObject *replacer, Value space, StringBuffer &sb)
     616                 : {
     617                 :     /* Step 4. */
     618           33732 :     AutoIdVector propertyList(cx);
     619           16866 :     if (replacer) {
     620            1860 :         if (replacer->isCallable()) {
     621                 :             /* Step 4a(i): use replacer to transform values.  */
     622            1860 :         } 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                 :             uint32_t len;
     653            1860 :             JS_ALWAYS_TRUE(js_GetLengthProperty(cx, replacer, &len));
     654            1860 :             if (replacer->isDenseArray())
     655            1851 :                 len = JS_MIN(len, replacer->getDenseArrayCapacity());
     656                 : 
     657            3720 :             HashSet<jsid> idSet(cx);
     658            1860 :             if (!idSet.init(len))
     659               0 :                 return false;
     660                 : 
     661                 :             /* Step 4b(iii). */
     662            1860 :             uint32_t i = 0;
     663                 : 
     664                 :             /* Step 4b(iv). */
     665            7431 :             for (; i < len; i++) {
     666                 :                 /* Step 4b(iv)(2). */
     667                 :                 Value v;
     668            5571 :                 if (!replacer->getElement(cx, i, &v))
     669               0 :                     return false;
     670                 : 
     671                 :                 jsid id;
     672            5571 :                 if (v.isNumber()) {
     673                 :                     /* Step 4b(iv)(4). */
     674                 :                     int32_t n;
     675              27 :                     if (v.isNumber() && ValueFitsInInt32(v, &n) && INT_FITS_IN_JSID(n)) {
     676              27 :                         id = INT_TO_JSID(n);
     677                 :                     } else {
     678               0 :                         if (!js_ValueToStringId(cx, v, &id))
     679               0 :                             return false;
     680               0 :                         id = js_CheckForStringIndex(id);
     681                 :                     }
     682            5589 :                 } else if (v.isString() ||
     683              18 :                            (v.isObject() &&
     684              18 :                             (ObjectClassIs(v.toObject(), ESClass_String, cx) ||
     685               9 :                              ObjectClassIs(v.toObject(), ESClass_Number, cx))))
     686                 :                 {
     687                 :                     /* Step 4b(iv)(3), 4b(iv)(5). */
     688            5544 :                     if (!js_ValueToStringId(cx, v, &id))
     689               0 :                         return false;
     690            5544 :                     id = js_CheckForStringIndex(id);
     691                 :                 } else {
     692               0 :                     continue;
     693                 :                 }
     694                 : 
     695                 :                 /* Step 4b(iv)(6). */
     696            5571 :                 HashSet<jsid>::AddPtr p = idSet.lookupForAdd(id);
     697            5571 :                 if (!p) {
     698                 :                     /* Step 4b(iv)(6)(a). */
     699            5571 :                     if (!idSet.add(p, id) || !propertyList.append(id))
     700               0 :                         return false;
     701                 :                 }
     702                 :             }
     703                 :         } else {
     704               0 :             replacer = NULL;
     705                 :         }
     706                 :     }
     707                 : 
     708                 :     /* Step 5. */
     709           16866 :     if (space.isObject()) {
     710              18 :         JSObject &spaceObj = space.toObject();
     711              18 :         if (ObjectClassIs(spaceObj, ESClass_Number, cx)) {
     712                 :             double d;
     713               9 :             if (!ToNumber(cx, space, &d))
     714               0 :                 return false;
     715               9 :             space = NumberValue(d);
     716               9 :         } else if (ObjectClassIs(spaceObj, ESClass_String, cx)) {
     717               9 :             JSString *str = ToStringSlow(cx, space);
     718               9 :             if (!str)
     719               0 :                 return false;
     720               9 :             space = StringValue(str);
     721                 :         }
     722                 :     }
     723                 : 
     724           33732 :     StringBuffer gap(cx);
     725                 : 
     726           16866 :     if (space.isNumber()) {
     727                 :         /* Step 6. */
     728                 :         double d;
     729             775 :         JS_ALWAYS_TRUE(ToInteger(cx, space, &d));
     730             775 :         d = JS_MIN(10, d);
     731             775 :         if (d >= 1 && !gap.appendN(' ', uint32_t(d)))
     732               0 :             return false;
     733           16091 :     } else if (space.isString()) {
     734                 :         /* Step 7. */
     735               9 :         JSLinearString *str = space.toString()->ensureLinear(cx);
     736               9 :         if (!str)
     737               0 :             return false;
     738              18 :         JS::Anchor<JSString *> anchor(str);
     739               9 :         size_t len = JS_MIN(10, space.toString()->length());
     740               9 :         if (!gap.append(str->chars(), len))
     741               0 :             return false;
     742                 :     } else {
     743                 :         /* Step 8. */
     744           16082 :         JS_ASSERT(gap.empty());
     745                 :     }
     746                 : 
     747                 :     /* Step 9. */
     748           16866 :     JSObject *wrapper = NewBuiltinClassInstance(cx, &ObjectClass);
     749           16866 :     if (!wrapper)
     750               0 :         return false;
     751                 : 
     752                 :     /* Step 10. */
     753           16866 :     jsid emptyId = ATOM_TO_JSID(cx->runtime->atomState.emptyAtom);
     754           16866 :     if (!DefineNativeProperty(cx, wrapper, emptyId, *vp, JS_PropertyStub, JS_StrictPropertyStub,
     755           16866 :                               JSPROP_ENUMERATE, 0, 0))
     756                 :     {
     757               0 :         return false;
     758                 :     }
     759                 : 
     760                 :     /* Step 11. */
     761           33732 :     StringifyContext scx(cx, sb, gap, replacer, propertyList);
     762           16866 :     if (!scx.init())
     763               0 :         return false;
     764                 : 
     765           16866 :     if (!PreprocessValue(cx, wrapper, emptyId, vp, &scx))
     766               0 :         return false;
     767           16866 :     if (IsFilteredValue(*vp))
     768              17 :         return true;
     769                 : 
     770           16849 :     return Str(cx, *vp, &scx);
     771                 : }
     772                 : 
     773                 : /* ES5 15.12.2 Walk. */
     774                 : static bool
     775               9 : Walk(JSContext *cx, JSObject *holder, jsid name, const Value &reviver, Value *vp)
     776                 : {
     777               9 :     JS_CHECK_RECURSION(cx, return false);
     778                 : 
     779                 :     /* Step 1. */
     780                 :     Value val;
     781               9 :     if (!holder->getGeneric(cx, name, &val))
     782               0 :         return false;
     783                 : 
     784                 :     /* Step 2. */
     785               9 :     if (val.isObject()) {
     786               3 :         JSObject *obj = &val.toObject();
     787                 : 
     788                 :         /* 'val' must have been produced by the JSON parser, so not a proxy. */
     789               3 :         JS_ASSERT(!obj->isProxy());
     790               3 :         if (obj->isArray()) {
     791                 :             /* Step 2a(ii). */
     792               2 :             uint32_t length = obj->getArrayLength();
     793                 : 
     794                 :             /* Step 2a(i), 2a(iii-iv). */
     795               3 :             for (uint32_t i = 0; i < length; i++) {
     796                 :                 jsid id;
     797               1 :                 if (!IndexToId(cx, i, &id))
     798               0 :                     return false;
     799                 : 
     800                 :                 /* Step 2a(iii)(1). */
     801                 :                 Value newElement;
     802               1 :                 if (!Walk(cx, obj, id, reviver, &newElement))
     803               0 :                     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               1 :                 if (newElement.isUndefined()) {
     817                 :                     /* Step 2a(iii)(2). */
     818               0 :                     JS_ALWAYS_TRUE(array_deleteElement(cx, obj, i, &newElement, false));
     819                 :                 } else {
     820                 :                     /* Step 2a(iii)(3). */
     821               1 :                     JS_ALWAYS_TRUE(array_defineElement(cx, obj, i, &newElement, JS_PropertyStub,
     822               1 :                                                        JS_StrictPropertyStub, JSPROP_ENUMERATE));
     823                 :                 }
     824                 :             }
     825                 :         } else {
     826                 :             /* Step 2b(i). */
     827               2 :             AutoIdVector keys(cx);
     828               1 :             if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, &keys))
     829               0 :                 return false;
     830                 : 
     831                 :             /* Step 2b(ii). */
     832               1 :             for (size_t i = 0, len = keys.length(); i < len; i++) {
     833                 :                 /* Step 2b(ii)(1). */
     834                 :                 Value newElement;
     835               0 :                 jsid id = keys[i];
     836               0 :                 if (!Walk(cx, obj, id, reviver, &newElement))
     837               0 :                     return false;
     838                 : 
     839               0 :                 if (newElement.isUndefined()) {
     840                 :                     /* Step 2b(ii)(2). */
     841               0 :                     if (!obj->deleteByValue(cx, IdToValue(id), &newElement, false))
     842               0 :                         return false;
     843                 :                 } else {
     844                 :                     /* Step 2b(ii)(3). */
     845               0 :                     JS_ASSERT(obj->isNative());
     846               0 :                     if (!DefineNativeProperty(cx, obj, id, newElement, JS_PropertyStub,
     847               0 :                                               JS_StrictPropertyStub, JSPROP_ENUMERATE, 0, 0))
     848                 :                     {
     849               0 :                         return false;
     850                 :                     }
     851                 :                 }
     852                 :             }
     853                 :         }
     854                 :     }
     855                 : 
     856                 :     /* Step 3. */
     857               9 :     JSString *key = IdToString(cx, name);
     858               9 :     if (!key)
     859               0 :         return false;
     860                 : 
     861              18 :     InvokeArgsGuard args;
     862               9 :     if (!cx->stack.pushInvokeArgs(cx, 2, &args))
     863               0 :         return false;
     864                 : 
     865               9 :     args.calleev() = reviver;
     866               9 :     args.thisv() = ObjectValue(*holder);
     867               9 :     args[0] = StringValue(key);
     868               9 :     args[1] = val;
     869                 : 
     870               9 :     if (!Invoke(cx, args))
     871               0 :         return false;
     872               9 :     *vp = args.rval();
     873               9 :     return true;
     874                 : }
     875                 : 
     876                 : static bool
     877               8 : Revive(JSContext *cx, const Value &reviver, Value *vp)
     878                 : {
     879                 : 
     880               8 :     JSObject *obj = NewBuiltinClassInstance(cx, &ObjectClass);
     881               8 :     if (!obj)
     882               0 :         return false;
     883                 : 
     884               8 :     if (!obj->defineProperty(cx, cx->runtime->atomState.emptyAtom, *vp))
     885               0 :         return false;
     886                 : 
     887               8 :     return Walk(cx, obj, ATOM_TO_JSID(cx->runtime->atomState.emptyAtom), reviver, vp);
     888                 : }
     889                 : 
     890                 : namespace js {
     891                 : 
     892                 : JSBool
     893            8518 : ParseJSONWithReviver(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            8518 :                       decodingMode == STRICT ? JSONParser::StrictJSON : JSONParser::LegacyJSON);
     899            8518 :     if (!parser.parse(vp))
     900             210 :         return false;
     901                 : 
     902                 :     /* 15.12.2 steps 4-5. */
     903            8308 :     if (js_IsCallable(reviver))
     904               8 :         return Revive(cx, reviver, vp);
     905            8300 :     return true;
     906                 : }
     907                 : 
     908                 : } /* namespace js */
     909                 : 
     910                 : #if JS_HAS_TOSOURCE
     911                 : static JSBool
     912               0 : json_toSource(JSContext *cx, unsigned argc, Value *vp)
     913                 : {
     914               0 :     vp->setString(CLASS_ATOM(cx, JSON));
     915               0 :     return JS_TRUE;
     916                 : }
     917                 : #endif
     918                 : 
     919                 : static 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                 : 
     928                 : JSObject *
     929            2925 : js_InitJSONClass(JSContext *cx, JSObject *obj)
     930                 : {
     931            2925 :     JSObject *JSON = NewObjectWithClassProto(cx, &JSONClass, NULL, obj);
     932            2925 :     if (!JSON || !JSON->setSingletonType(cx))
     933               0 :         return NULL;
     934                 : 
     935            2925 :     if (!JS_DefineProperty(cx, obj, js_JSON_str, OBJECT_TO_JSVAL(JSON),
     936            2925 :                            JS_PropertyStub, JS_StrictPropertyStub, 0))
     937               0 :         return NULL;
     938                 : 
     939            2925 :     if (!JS_DefineFunctions(cx, JSON, json_static_methods))
     940               0 :         return NULL;
     941                 : 
     942            2925 :     MarkStandardClassInitializedNoProto(obj, &JSONClass);
     943                 : 
     944            2925 :     return JSON;
     945                 : }

Generated by: LCOV version 1.7