LCOV - app.info - js/src/json.cpp

LCOV - code coverage report
| Current view: | directory - js/src - json.cpp (source / functions) |  | Found | Hit | Coverage |
| Test: | app.info | Lines: | 390 | 259 | 66.4 %
| Date: | 2012-04-07 | Functions: | 25 | 22 | 88.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                 : #include "vm/StringBuffer.h"
          63                 :
          64                 : #include "jsatominlines.h"
          65                 : #include "jsboolinlines.h"
          66                 : #include "jsinferinlines.h"
          67                 : #include "jsobjinlines.h"
          68                 :
          69                 : #include "vm/Stack-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             225 : js_json_parse(JSContext *cx, unsigned argc, Value *vp)
          90                 : {
          91                 :     /* Step 1. */
          92                 :     JSLinearString *linear;
          93             225 :     if (argc >= 1) {
          94              45 :         JSString *str = ToString(cx, vp[2]);
          95              45 :         if (!str)
          96               0 :             return false;
          97              45 :         linear = str->ensureLinear(cx);
          98              45 :         if (!linear)
          99               0 :             return false;
         100                 :     } else {
         101             180 :         linear = cx->runtime->atomState.typeAtoms[JSTYPE_VOID];
         102                 :     }
         103             450 :     JS::Anchor<JSString *> anchor(linear);
         104                 :
         105             225 :     Value reviver = (argc >= 2) ? vp[3] : UndefinedValue();
         106                 :
         107                 :     /* Steps 2-5. */
         108             225 :     return ParseJSONWithReviver(cx, linear->chars(), linear->length(), reviver, vp);
         109                 : }
         110                 :
         111                 : /* ES5 15.12.3. */
         112                 : JSBool
         113             315 : js_json_stringify(JSContext *cx, unsigned argc, Value *vp)
         114                 : {
         115             315 :     *vp = (argc >= 1) ? vp[2] : UndefinedValue();
         116              36 :     JSObject *replacer = (argc >= 2 && vp[3].isObject())
         117              18 :                          ? &vp[3].toObject()
         118             369 :                          : NULL;
         119             315 :     Value space = (argc >= 3) ? vp[4] : UndefinedValue();
         120                 :
         121             630 :     StringBuffer sb(cx);
         122             315 :     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             315 :     if (!sb.empty()) {
         129             306 :         JSString *str = sb.finishString();
         130             306 :         if (!str)
         131               0 :             return false;
         132             306 :         vp->setString(str);
         133                 :     } else {
         134               9 :         vp->setUndefined();
         135                 :     }
         136                 :
         137             315 :     return true;
         138                 : }
         139                 :
         140            2511 : 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            2511 :     return c == '"' || c == '\\' || c < ' ';
         148                 : }
         149                 :
         150                 : /* ES5 15.12.3 Quote. */
         151                 : static bool
         152             585 : Quote(JSContext *cx, StringBuffer &sb, JSString *str)
         153                 : {
         154            1170 :     JS::Anchor<JSString *> anchor(str);
         155             585 :     size_t len = str->length();
         156             585 :     const jschar *buf = str->getChars(cx);
         157             585 :     if (!buf)
         158               0 :         return false;
         159                 :
         160                 :     /* Step 1. */
         161             585 :     if (!sb.append('"'))
         162               0 :         return false;
         163                 :
         164                 :     /* Step 2. */
         165             585 :     for (size_t i = 0; i < len; ++i) {
         166                 :         /* Batch-append maximal character sequences containing no escapes. */
         167             585 :         size_t mark = i;
         168            2511 :         do {
         169            2511 :             if (IsQuoteSpecialCharacter(buf[i]))
         170               0 :                 break;
         171                 :         } while (++i < len);
         172             585 :         if (i > mark) {
         173             585 :             if (!sb.append(&buf[mark], i - mark))
         174               0 :                 return false;
         175             585 :             if (i == len)
         176             585 :                 break;
         177                 :         }
         178                 :
         179               0 :         jschar c = buf[i];
         180               0 :         if (c == '"' || c == '\\') {
         181               0 :             if (!sb.append('\\') || !sb.append(c))
         182               0 :                 return false;
         183               0 :         } 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               0 :                          : 't';
         193               0 :            if (!sb.append('\\') || !sb.append(abbrev))
         194               0 :                return false;
         195                 :         } else {
         196               0 :             JS_ASSERT(c < ' ');
         197               0 :             if (!sb.append("\\u00"))
         198               0 :                 return false;
         199               0 :             JS_ASSERT((c >> 4) < 10);
         200               0 :             uint8_t x = c >> 4, y = c % 16;
         201               0 :             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             585 :     return sb.append('"');
         208                 : }
         209                 :
         210                 : class StringifyContext
         211                 : {
         212                 :   public:
         213             315 :     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             315 :         objectStack(cx)
         221             315 :     {}
         222                 :
         223             315 :     bool init() {
         224             315 :         return objectStack.init(16);
         225                 :     }
         226                 :
         227                 : #ifdef DEBUG
         228             315 :     ~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             855 : WriteIndent(JSContext *cx, StringifyContext *scx, uint32_t limit)
         243                 : {
         244             855 :     if (!scx->gap.empty()) {
         245              54 :         if (!scx->sb.append('\n'))
         246               0 :             return JS_FALSE;
         247              90 :         for (uint32_t i = 0; i < limit; i++) {
         248              36 :             if (!scx->sb.append(scx->gap.begin(), scx->gap.end()))
         249               0 :                 return JS_FALSE;
         250                 :         }
         251                 :     }
         252                 :
         253             855 :     return JS_TRUE;
         254                 : }
         255                 :
         256                 : class CycleDetector
         257                 : {
         258                 :   public:
         259             315 :     CycleDetector(StringifyContext *scx, JSObject *obj)
         260             315 :       : objectStack(scx->objectStack), obj(obj) {
         261             315 :     }
         262                 :
         263             315 :     bool init(JSContext *cx) {
         264             630 :         HashSet<JSObject *>::AddPtr ptr = objectStack.lookupForAdd(obj);
         265             315 :         if (ptr) {
         266               0 :             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CYCLIC_VALUE, js_object_str);
         267               0 :             return false;
         268                 :         }
         269             315 :         return objectStack.add(ptr, obj);
         270                 :     }
         271                 :
         272             315 :     ~CycleDetector() {
         273             315 :         objectStack.remove(obj);
         274             315 :     }
         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               0 :     static JSString *toString(JSContext *cx, uint32_t index) {
         289               0 :         return IndexToString(cx, index);
         290                 :     }
         291                 : };
         292                 :
         293                 : template<>
         294                 : class KeyStringifier<jsid> {
         295                 :   public:
         296               0 :     static JSString *toString(JSContext *cx, jsid id) {
         297               0 :         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             927 : PreprocessValue(JSContext *cx, JSObject *holder, KeyType key, Value *vp, StringifyContext *scx)
         308                 : {
         309             927 :     JSString *keyStr = NULL;
         310                 :
         311                 :     /* Step 2. */
         312             927 :     if (vp->isObject()) {
         313                 :         Value toJSON;
         314             351 :         jsid id = ATOM_TO_JSID(cx->runtime->atomState.toJSONAtom);
         315             351 :         if (!js_GetMethod(cx, &vp->toObject(), id, 0, &toJSON))
         316               0 :             return false;
         317                 :
         318             351 :         if (js_IsCallable(toJSON)) {
         319               0 :             keyStr = KeyStringifier<KeyType>::toString(cx, key);
         320               0 :             if (!keyStr)
         321               0 :                 return false;
         322                 :
         323               0 :             InvokeArgsGuard args;
         324               0 :             if (!cx->stack.pushInvokeArgs(cx, 1, &args))
         325               0 :                 return false;
         326                 :
         327               0 :             args.calleev() = toJSON;
         328               0 :             args.thisv() = *vp;
         329               0 :             args[0] = StringValue(keyStr);
         330                 :
         331               0 :             if (!Invoke(cx, args))
         332               0 :                 return false;
         333               0 :             *vp = args.rval();
         334                 :         }
         335                 :     }
         336                 :
         337                 :     /* Step 3. */
         338             927 :     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             927 :     if (vp->isObject()) {
         361             351 :         JSObject &obj = vp->toObject();
         362             351 :         if (ObjectClassIs(obj, ESClass_Number, cx)) {
         363                 :             double d;
         364               9 :             if (!ToNumber(cx, *vp, &d))
         365               0 :                 return false;
         366               9 :             vp->setNumber(d);
         367             342 :         } 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             333 :         } 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             927 :     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            1827 : IsFilteredValue(const Value &v)
         391                 : {
         392            1827 :     return v.isUndefined() || js_IsCallable(v) || (v.isObject() && v.toObject().isXML());
         393                 : }
         394                 :
         395                 : /* ES5 15.12.3 JO. */
         396                 : static JSBool
         397              54 : 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             108 :     CycleDetector detect(scx, obj);
         411              54 :     if (!detect.init(cx))
         412               0 :         return JS_FALSE;
         413                 :
         414              54 :     if (!scx->sb.append('{'))
         415               0 :         return JS_FALSE;
         416                 :
         417                 :     /* Steps 5-7. */
         418             108 :     Maybe<AutoIdVector> ids;
         419                 :     const AutoIdVector *props;
         420              54 :     if (scx->replacer && !scx->replacer->isCallable()) {
         421              18 :         JS_ASSERT(JS_IsArrayObject(cx, scx->replacer));
         422              18 :         props = &scx->propertyList;
         423                 :     } else {
         424              36 :         JS_ASSERT_IF(scx->replacer, scx->propertyList.length() == 0);
         425              36 :         ids.construct(cx);
         426              36 :         if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, ids.addr()))
         427               0 :             return false;
         428              36 :         props = ids.addr();
         429                 :     }
         430                 :
         431                 :     /* My kingdom for not-quite-initialized-from-the-start references. */
         432              54 :     const AutoIdVector &propertyList = *props;
         433                 :
         434                 :     /* Steps 8-10, 13. */
         435              54 :     bool wroteMember = false;
         436             171 :     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             117 :         const jsid &id = propertyList[i];
         445                 :         Value outputValue;
         446             117 :         if (!obj->getGeneric(cx, id, &outputValue))
         447               0 :             return false;
         448             117 :         if (!PreprocessValue(cx, obj, id, &outputValue, scx))
         449               0 :             return false;
         450             117 :         if (IsFilteredValue(outputValue))
         451              18 :             continue;
         452                 :
         453                 :         /* Output a comma unless this is the first member to write. */
         454              99 :         if (wroteMember && !scx->sb.append(','))
         455               0 :             return false;
         456              99 :         wroteMember = true;
         457                 :
         458              99 :         if (!WriteIndent(cx, scx, scx->depth))
         459               0 :             return false;
         460                 :
         461              99 :         JSString *s = IdToString(cx, id);
         462              99 :         if (!s)
         463               0 :             return false;
         464                 :
         465             396 :         if (!Quote(cx, scx->sb, s) ||
         466              99 :             !scx->sb.append(':') ||
         467             135 :             !(scx->gap.empty() || scx->sb.append(' ')) ||
         468              99 :             !Str(cx, outputValue, scx))
         469                 :         {
         470               0 :             return false;
         471                 :         }
         472                 :     }
         473                 :
         474              54 :     if (wroteMember && !WriteIndent(cx, scx, scx->depth - 1))
         475               0 :         return false;
         476                 :
         477              54 :     return scx->sb.append('}');
         478                 : }
         479                 :
         480                 : /* ES5 15.12.3 JA. */
         481                 : static JSBool
         482             261 : 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             522 :     CycleDetector detect(scx, obj);
         496             261 :     if (!detect.init(cx))
         497               0 :         return JS_FALSE;
         498                 :
         499             261 :     if (!scx->sb.append('['))
         500               0 :         return JS_FALSE;
         501                 :
         502                 :     /* Step 6. */
         503                 :     uint32_t length;
         504             261 :     if (!js_GetLengthProperty(cx, obj, &length))
         505               0 :         return JS_FALSE;
         506                 :
         507                 :     /* Steps 7-10. */
         508             261 :     if (length != 0) {
         509                 :         /* Steps 4, 10b(i). */
         510             207 :         if (!WriteIndent(cx, scx, scx->depth))
         511               0 :             return JS_FALSE;
         512                 :
         513                 :         /* Steps 7-10. */
         514                 :         Value outputValue;
         515             702 :         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             495 :             if (!obj->getElement(cx, i, &outputValue))
         523               0 :                 return JS_FALSE;
         524             495 :             if (!PreprocessValue(cx, obj, i, &outputValue, scx))
         525               0 :                 return JS_FALSE;
         526             495 :             if (IsFilteredValue(outputValue)) {
         527               0 :                 if (!scx->sb.append("null"))
         528               0 :                     return JS_FALSE;
         529                 :             } else {
         530             495 :                 if (!Str(cx, outputValue, scx))
         531               0 :                     return JS_FALSE;
         532                 :             }
         533                 :
         534                 :             /* Steps 3, 4, 10b(i). */
         535             495 :             if (i < length - 1) {
         536             288 :                 if (!scx->sb.append(','))
         537               0 :                     return JS_FALSE;
         538             288 :                 if (!WriteIndent(cx, scx, scx->depth))
         539               0 :                     return JS_FALSE;
         540                 :             }
         541                 :         }
         542                 :
         543                 :         /* Step 10(b)(iii). */
         544             207 :         if (!WriteIndent(cx, scx, scx->depth - 1))
         545               0 :             return JS_FALSE;
         546                 :     }
         547                 :
         548             261 :     return scx->sb.append(']');
         549                 : }
         550                 :
         551                 : static JSBool
         552             900 : Str(JSContext *cx, const Value &v, StringifyContext *scx)
         553                 : {
         554                 :     /* Step 11 must be handled by the caller. */
         555             900 :     JS_ASSERT(!IsFilteredValue(v));
         556                 :
         557             900 :     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             900 :     if (v.isString())
         574             486 :         return Quote(cx, scx->sb, v.toString());
         575                 :
         576                 :     /* Step 5. */
         577             414 :     if (v.isNull())
         578               0 :         return scx->sb.append("null");
         579                 :
         580                 :     /* Steps 6-7. */
         581             414 :     if (v.isBoolean())
         582              63 :         return v.toBoolean() ? scx->sb.append("true") : scx->sb.append("false");
         583                 :
         584                 :     /* Step 9. */
         585             351 :     if (v.isNumber()) {
         586              36 :         if (v.isDouble()) {
         587               0 :             if (!JSDOUBLE_IS_FINITE(v.toDouble()))
         588               0 :                 return scx->sb.append("null");
         589                 :         }
         590                 :
         591              72 :         StringBuffer sb(cx);
         592              36 :         if (!NumberValueToStringBuffer(cx, v, sb))
         593               0 :             return false;
         594                 :
         595              36 :         return scx->sb.append(sb.begin(), sb.length());
         596                 :     }
         597                 :
         598                 :     /* Step 10. */
         599             315 :     JS_ASSERT(v.isObject());
         600             315 :     JSObject *obj = &v.toObject();
         601                 :
         602             315 :     scx->depth++;
         603                 :     JSBool ok;
         604             315 :     if (ObjectClassIs(v.toObject(), ESClass_Array, cx))
         605             261 :         ok = JA(cx, obj, scx);
         606                 :     else
         607              54 :         ok = JO(cx, obj, scx);
         608             315 :     scx->depth--;
         609                 :
         610             315 :     return ok;
         611                 : }
         612                 :
         613                 : /* ES5 15.12.3. */
         614                 : JSBool
         615             315 : js_Stringify(JSContext *cx, Value *vp, JSObject *replacer, Value space, StringBuffer &sb)
         616                 : {
         617                 :     /* Step 4. */
         618             630 :     AutoIdVector propertyList(cx);
         619             315 :     if (replacer) {
         620              18 :         if (replacer->isCallable()) {
         621                 :             /* Step 4a(i): use replacer to transform values.  */
         622              18 :         } 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              18 :             JS_ALWAYS_TRUE(js_GetLengthProperty(cx, replacer, &len));
         654              18 :             if (replacer->isDenseArray())
         655               9 :                 len = JS_MIN(len, replacer->getDenseArrayCapacity());
         656                 :
         657              36 :             HashSet<jsid> idSet(cx);
         658              18 :             if (!idSet.init(len))
         659               0 :                 return false;
         660                 :
         661                 :             /* Step 4b(iii). */
         662              18 :             uint32_t i = 0;
         663                 :
         664                 :             /* Step 4b(iv). */
         665              63 :             for (; i < len; i++) {
         666                 :                 /* Step 4b(iv)(2). */
         667                 :                 Value v;
         668              45 :                 if (!replacer->getElement(cx, i, &v))
         669               0 :                     return false;
         670                 :
         671                 :                 jsid id;
         672              45 :                 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              63 :                 } 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              18 :                     if (!js_ValueToStringId(cx, v, &id))
         689               0 :                         return false;
         690              18 :                     id = js_CheckForStringIndex(id);
         691                 :                 } else {
         692               0 :                     continue;
         693                 :                 }
         694                 :
         695                 :                 /* Step 4b(iv)(6). */
         696              90 :                 HashSet<jsid>::AddPtr p = idSet.lookupForAdd(id);
         697              45 :                 if (!p) {
         698                 :                     /* Step 4b(iv)(6)(a). */
         699              45 :                     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             315 :     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             630 :     StringBuffer gap(cx);
         725                 :
         726             315 :     if (space.isNumber()) {
         727                 :         /* Step 6. */
         728                 :         double d;
         729               9 :         JS_ALWAYS_TRUE(ToInteger(cx, space, &d));
         730               9 :         d = JS_MIN(10, d);
         731               9 :         if (d >= 1 && !gap.appendN(' ', uint32_t(d)))
         732               0 :             return false;
         733             306 :     } 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             297 :         JS_ASSERT(gap.empty());
         745                 :     }
         746                 :
         747                 :     /* Step 9. */
         748             315 :     JSObject *wrapper = NewBuiltinClassInstance(cx, &ObjectClass);
         749             315 :     if (!wrapper)
         750               0 :         return false;
         751                 :
         752                 :     /* Step 10. */
         753             315 :     jsid emptyId = ATOM_TO_JSID(cx->runtime->atomState.emptyAtom);
         754             315 :     if (!DefineNativeProperty(cx, wrapper, emptyId, *vp, JS_PropertyStub, JS_StrictPropertyStub,
         755             315 :                               JSPROP_ENUMERATE, 0, 0))
         756                 :     {
         757               0 :         return false;
         758                 :     }
         759                 :
         760                 :     /* Step 11. */
         761             630 :     StringifyContext scx(cx, sb, gap, replacer, propertyList);
         762             315 :     if (!scx.init())
         763               0 :         return false;
         764                 :
         765             315 :     if (!PreprocessValue(cx, wrapper, emptyId, vp, &scx))
         766               0 :         return false;
         767             315 :     if (IsFilteredValue(*vp))
         768               9 :         return true;
         769                 :
         770             306 :     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             263 : 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             263 :                       decodingMode == STRICT ? JSONParser::StrictJSON : JSONParser::LegacyJSON);
         899             263 :     if (!parser.parse(vp))
         900             199 :         return false;
         901                 :
         902                 :     /* 15.12.2 steps 4-5. */
         903              64 :     if (js_IsCallable(reviver))
         904               8 :         return Revive(cx, reviver, vp);
         905              56 :     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            1032 : js_InitJSONClass(JSContext *cx, JSObject *obj)
         930                 : {
         931            1032 :     JSObject *JSON = NewObjectWithClassProto(cx, &JSONClass, NULL, obj);
         932            1032 :     if (!JSON || !JSON->setSingletonType(cx))
         933               0 :         return NULL;
         934                 :
         935            1032 :     if (!JS_DefineProperty(cx, obj, js_JSON_str, OBJECT_TO_JSVAL(JSON),
         936            1032 :                            JS_PropertyStub, JS_StrictPropertyStub, 0))
         937               0 :         return NULL;
         938                 :
         939            1032 :     if (!JS_DefineFunctions(cx, JSON, json_static_methods))
         940               0 :         return NULL;
         941                 :
         942            1032 :     MarkStandardClassInitializedNoProto(obj, &JSONClass);
         943                 :
         944            1032 :     return JSON;
         945                 : }

Generated by: LCOV version 1.7