LCOV - code coverage report
Current view: top level - js/src - json.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 224 444 50.5 %
Date: 2017-07-14 16:53:18 Functions: 24 28 85.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
       2             :  * vim: set ts=8 sts=4 et sw=4 tw=99:
       3             :  * This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include "json.h"
       8             : 
       9             : #include "mozilla/FloatingPoint.h"
      10             : #include "mozilla/Range.h"
      11             : #include "mozilla/ScopeExit.h"
      12             : 
      13             : #include "jsarray.h"
      14             : #include "jsatom.h"
      15             : #include "jscntxt.h"
      16             : #include "jsnum.h"
      17             : #include "jsobj.h"
      18             : #include "jsstr.h"
      19             : #include "jstypes.h"
      20             : #include "jsutil.h"
      21             : 
      22             : #include "vm/Interpreter.h"
      23             : #include "vm/JSONParser.h"
      24             : #include "vm/StringBuffer.h"
      25             : 
      26             : #include "jsarrayinlines.h"
      27             : #include "jsatominlines.h"
      28             : #include "jsboolinlines.h"
      29             : 
      30             : #include "vm/NativeObject-inl.h"
      31             : 
      32             : using namespace js;
      33             : using namespace js::gc;
      34             : 
      35             : using mozilla::IsFinite;
      36             : using mozilla::Maybe;
      37             : using mozilla::RangedPtr;
      38             : 
      39             : const Class js::JSONClass = {
      40             :     js_JSON_str,
      41             :     JSCLASS_HAS_CACHED_PROTO(JSProto_JSON)
      42             : };
      43             : 
      44             : static inline bool
      45        6282 : IsQuoteSpecialCharacter(char16_t c)
      46             : {
      47             :     static_assert('\b' < ' ', "'\\b' must be treated as special below");
      48             :     static_assert('\f' < ' ', "'\\f' must be treated as special below");
      49             :     static_assert('\n' < ' ', "'\\n' must be treated as special below");
      50             :     static_assert('\r' < ' ', "'\\r' must be treated as special below");
      51             :     static_assert('\t' < ' ', "'\\t' must be treated as special below");
      52             : 
      53        6282 :     return c == '"' || c == '\\' || c < ' ';
      54             : }
      55             : 
      56             : /* ES5 15.12.3 Quote. */
      57             : template <typename CharT>
      58             : static bool
      59         364 : Quote(StringBuffer& sb, JSLinearString* str)
      60             : {
      61         364 :     size_t len = str->length();
      62             : 
      63             :     /* Step 1. */
      64         364 :     if (!sb.append('"'))
      65           0 :         return false;
      66             : 
      67             :     /* Step 2. */
      68         728 :     JS::AutoCheckCannotGC nogc;
      69         364 :     const RangedPtr<const CharT> buf(str->chars<CharT>(nogc), len);
      70         373 :     for (size_t i = 0; i < len; ++i) {
      71             :         /* Batch-append maximal character sequences containing no escapes. */
      72         368 :         size_t mark = i;
      73        6273 :         do {
      74        6282 :             if (IsQuoteSpecialCharacter(buf[i]))
      75           9 :                 break;
      76             :         } while (++i < len);
      77         368 :         if (i > mark) {
      78         368 :             if (!sb.appendSubstring(str, mark, i - mark))
      79           0 :                 return false;
      80         368 :             if (i == len)
      81         359 :                 break;
      82             :         }
      83             : 
      84           9 :         char16_t c = buf[i];
      85           9 :         if (c == '"' || c == '\\') {
      86           0 :             if (!sb.append('\\') || !sb.append(c))
      87           0 :                 return false;
      88           9 :         } else if (c == '\b' || c == '\f' || c == '\n' || c == '\r' || c == '\t') {
      89             :            char16_t abbrev = (c == '\b')
      90             :                          ? 'b'
      91             :                          : (c == '\f')
      92             :                          ? 'f'
      93             :                          : (c == '\n')
      94             :                          ? 'n'
      95             :                          : (c == '\r')
      96             :                          ? 'r'
      97           9 :                          : 't';
      98           9 :            if (!sb.append('\\') || !sb.append(abbrev))
      99           0 :                return false;
     100             :         } else {
     101           0 :             MOZ_ASSERT(c < ' ');
     102           0 :             if (!sb.append("\\u00"))
     103           0 :                 return false;
     104           0 :             MOZ_ASSERT((c >> 4) < 10);
     105           0 :             uint8_t x = c >> 4, y = c % 16;
     106           0 :             if (!sb.append(Latin1Char('0' + x)) ||
     107           0 :                 !sb.append(Latin1Char(y < 10 ? '0' + y : 'a' + (y - 10))))
     108             :             {
     109           0 :                 return false;
     110             :             }
     111             :         }
     112             :     }
     113             : 
     114             :     /* Steps 3-4. */
     115         364 :     return sb.append('"');
     116             : }
     117             : 
     118             : static bool
     119         364 : Quote(JSContext* cx, StringBuffer& sb, JSString* str)
     120             : {
     121         364 :     JSLinearString* linear = str->ensureLinear(cx);
     122         364 :     if (!linear)
     123           0 :         return false;
     124             : 
     125         364 :     return linear->hasLatin1Chars()
     126         364 :            ? Quote<Latin1Char>(sb, linear)
     127         364 :            : Quote<char16_t>(sb, linear);
     128             : }
     129             : 
     130             : namespace {
     131             : 
     132             : using ObjectVector = GCVector<JSObject*, 8>;
     133             : 
     134          15 : class StringifyContext
     135             : {
     136             :   public:
     137          15 :     StringifyContext(JSContext* cx, StringBuffer& sb, const StringBuffer& gap,
     138             :                      HandleObject replacer, const AutoIdVector& propertyList,
     139             :                      bool maybeSafely)
     140          15 :       : sb(sb),
     141             :         gap(gap),
     142             :         replacer(cx, replacer),
     143          30 :         stack(cx, ObjectVector(cx)),
     144             :         propertyList(propertyList),
     145             :         depth(0),
     146          45 :         maybeSafely(maybeSafely)
     147             :     {
     148          15 :         MOZ_ASSERT_IF(maybeSafely, !replacer);
     149          15 :         MOZ_ASSERT_IF(maybeSafely, gap.empty());
     150          15 :     }
     151             : 
     152             :     StringBuffer& sb;
     153             :     const StringBuffer& gap;
     154             :     RootedObject replacer;
     155             :     Rooted<ObjectVector> stack;
     156             :     const AutoIdVector& propertyList;
     157             :     uint32_t depth;
     158             :     bool maybeSafely;
     159             : };
     160             : 
     161             : } /* anonymous namespace */
     162             : 
     163             : static bool Str(JSContext* cx, const Value& v, StringifyContext* scx);
     164             : 
     165             : static bool
     166         369 : WriteIndent(JSContext* cx, StringifyContext* scx, uint32_t limit)
     167             : {
     168         369 :     if (!scx->gap.empty()) {
     169          79 :         if (!scx->sb.append('\n'))
     170           0 :             return false;
     171             : 
     172          79 :         if (scx->gap.isUnderlyingBufferLatin1()) {
     173         204 :             for (uint32_t i = 0; i < limit; i++) {
     174         125 :                 if (!scx->sb.append(scx->gap.rawLatin1Begin(), scx->gap.rawLatin1End()))
     175           0 :                     return false;
     176             :             }
     177             :         } else {
     178           0 :             for (uint32_t i = 0; i < limit; i++) {
     179           0 :                 if (!scx->sb.append(scx->gap.rawTwoByteBegin(), scx->gap.rawTwoByteEnd()))
     180           0 :                     return false;
     181             :             }
     182             :         }
     183             :     }
     184             : 
     185         369 :     return true;
     186             : }
     187             : 
     188             : namespace {
     189             : 
     190             : template<typename KeyType>
     191             : class KeyStringifier {
     192             : };
     193             : 
     194             : template<>
     195             : class KeyStringifier<uint32_t> {
     196             :   public:
     197          74 :     static JSString* toString(JSContext* cx, uint32_t index) {
     198          74 :         return IndexToString(cx, index);
     199             :     }
     200             : };
     201             : 
     202             : template<>
     203             : class KeyStringifier<HandleId> {
     204             :   public:
     205          36 :     static JSString* toString(JSContext* cx, HandleId id) {
     206          36 :         return IdToString(cx, id);
     207             :     }
     208             : };
     209             : 
     210             : } /* anonymous namespace */
     211             : 
     212             : /*
     213             :  * ES5 15.12.3 Str, steps 2-4, extracted to enable preprocessing of property
     214             :  * values when stringifying objects in JO.
     215             :  */
     216             : template<typename KeyType>
     217             : static bool
     218         315 : PreprocessValue(JSContext* cx, HandleObject holder, KeyType key, MutableHandleValue vp, StringifyContext* scx)
     219             : {
     220             :     // We don't want to do any preprocessing here if scx->maybeSafely,
     221             :     // since the stuff we do here can have side-effects.
     222         315 :     if (scx->maybeSafely)
     223           0 :         return true;
     224             : 
     225         630 :     RootedString keyStr(cx);
     226             : 
     227             :     /* Step 2. */
     228         315 :     if (vp.isObject()) {
     229         152 :         RootedValue toJSON(cx);
     230         152 :         RootedObject obj(cx, &vp.toObject());
     231          76 :         if (!GetProperty(cx, obj, obj, cx->names().toJSON, &toJSON))
     232           0 :             return false;
     233             : 
     234          76 :         if (IsCallable(toJSON)) {
     235           0 :             keyStr = KeyStringifier<KeyType>::toString(cx, key);
     236           0 :             if (!keyStr)
     237           0 :                 return false;
     238             : 
     239           0 :             RootedValue arg0(cx, StringValue(keyStr));
     240           0 :             if (!js::Call(cx, toJSON, vp, arg0, vp))
     241           0 :                 return false;
     242             :         }
     243             :     }
     244             : 
     245             :     /* Step 3. */
     246         315 :     if (scx->replacer && scx->replacer->isCallable()) {
     247         110 :         if (!keyStr) {
     248         110 :             keyStr = KeyStringifier<KeyType>::toString(cx, key);
     249         110 :             if (!keyStr)
     250           0 :                 return false;
     251             :         }
     252             : 
     253         220 :         RootedValue arg0(cx, StringValue(keyStr));
     254         220 :         RootedValue replacerVal(cx, ObjectValue(*scx->replacer));
     255         110 :         if (!js::Call(cx, replacerVal, holder, arg0, vp, vp))
     256           0 :             return false;
     257             :     }
     258             : 
     259             :     /* Step 4. */
     260         315 :     if (vp.get().isObject()) {
     261         152 :         RootedObject obj(cx, &vp.get().toObject());
     262             : 
     263             :         ESClass cls;
     264          76 :         if (!GetBuiltinClass(cx, obj, &cls))
     265           0 :             return false;
     266             : 
     267          76 :         if (cls == ESClass::Number) {
     268             :             double d;
     269           0 :             if (!ToNumber(cx, vp, &d))
     270           0 :                 return false;
     271           0 :             vp.setNumber(d);
     272          76 :         } else if (cls == ESClass::String) {
     273           0 :             JSString* str = ToStringSlow<CanGC>(cx, vp);
     274           0 :             if (!str)
     275           0 :                 return false;
     276           0 :             vp.setString(str);
     277          76 :         } else if (cls == ESClass::Boolean) {
     278           0 :             if (!Unbox(cx, obj, vp))
     279           0 :                 return false;
     280             :         }
     281             :     }
     282             : 
     283         315 :     return true;
     284             : }
     285             : 
     286             : /*
     287             :  * Determines whether a value which has passed by ES5 150.2.3 Str steps 1-4's
     288             :  * gauntlet will result in Str returning |undefined|.  This function is used to
     289             :  * properly omit properties resulting in such values when stringifying objects,
     290             :  * while properly stringifying such properties as null when they're encountered
     291             :  * in arrays.
     292             :  */
     293             : static inline bool
     294         630 : IsFilteredValue(const Value& v)
     295             : {
     296         630 :     return v.isUndefined() || v.isSymbol() || IsCallable(v);
     297             : }
     298             : 
     299             : class CycleDetector
     300             : {
     301             :   public:
     302          76 :     CycleDetector(StringifyContext* scx, HandleObject obj)
     303          76 :       : stack_(&scx->stack), obj_(obj), appended_(false) {
     304          76 :     }
     305             : 
     306          76 :     MOZ_ALWAYS_INLINE bool foundCycle(JSContext* cx) {
     307          76 :         JSObject* obj = obj_;
     308         183 :         for (JSObject* obj2 : stack_) {
     309         107 :             if (MOZ_UNLIKELY(obj == obj2)) {
     310           0 :                 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_JSON_CYCLIC_VALUE);
     311           0 :                 return false;
     312             :             }
     313             :         }
     314          76 :         appended_ = stack_.append(obj);
     315          76 :         return appended_;
     316             :     }
     317             : 
     318         152 :     ~CycleDetector() {
     319          76 :         if (MOZ_LIKELY(appended_)) {
     320          76 :             MOZ_ASSERT(stack_.back() == obj_);
     321          76 :             stack_.popBack();
     322             :         }
     323          76 :     }
     324             : 
     325             :   private:
     326             :     MutableHandle<ObjectVector> stack_;
     327             :     HandleObject obj_;
     328             :     bool appended_;
     329             : };
     330             : 
     331             : /* ES5 15.12.3 JO. */
     332             : static bool
     333          48 : JO(JSContext* cx, HandleObject obj, StringifyContext* scx)
     334             : {
     335             :     /*
     336             :      * This method implements the JO algorithm in ES5 15.12.3, but:
     337             :      *
     338             :      *   * The algorithm is somewhat reformulated to allow the final string to
     339             :      *     be streamed into a single buffer, rather than be created and copied
     340             :      *     into place incrementally as the ES5 algorithm specifies it.  This
     341             :      *     requires moving portions of the Str call in 8a into this algorithm
     342             :      *     (and in JA as well).
     343             :      */
     344             : 
     345          48 :     MOZ_ASSERT_IF(scx->maybeSafely, obj->is<PlainObject>());
     346             : 
     347             :     /* Steps 1-2, 11. */
     348          96 :     CycleDetector detect(scx, obj);
     349          48 :     if (!detect.foundCycle(cx))
     350           0 :         return false;
     351             : 
     352          48 :     if (!scx->sb.append('{'))
     353           0 :         return false;
     354             : 
     355             :     /* Steps 5-7. */
     356          96 :     Maybe<AutoIdVector> ids;
     357             :     const AutoIdVector* props;
     358          48 :     if (scx->replacer && !scx->replacer->isCallable()) {
     359             :         // NOTE: We can't assert |IsArray(scx->replacer)| because the replacer
     360             :         //       might have been a revocable proxy to an array.  Such a proxy
     361             :         //       satisfies |IsArray|, but any side effect of JSON.stringify
     362             :         //       could revoke the proxy so that |!IsArray(scx->replacer)|.  See
     363             :         //       bug 1196497.
     364           0 :         props = &scx->propertyList;
     365             :     } else {
     366          48 :         MOZ_ASSERT_IF(scx->replacer, scx->propertyList.length() == 0);
     367          48 :         ids.emplace(cx);
     368          48 :         if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY, ids.ptr()))
     369           0 :             return false;
     370          48 :         props = ids.ptr();
     371             :     }
     372             : 
     373             :     /* My kingdom for not-quite-initialized-from-the-start references. */
     374          48 :     const AutoIdVector& propertyList = *props;
     375             : 
     376             :     /* Steps 8-10, 13. */
     377          48 :     bool wroteMember = false;
     378          96 :     RootedId id(cx);
     379         263 :     for (size_t i = 0, len = propertyList.length(); i < len; i++) {
     380         215 :         if (!CheckForInterrupt(cx))
     381           0 :             return false;
     382             : 
     383             :         /*
     384             :          * Steps 8a-8b.  Note that the call to Str is broken up into 1) getting
     385             :          * the property; 2) processing for toJSON, calling the replacer, and
     386             :          * handling boxed Number/String/Boolean objects; 3) filtering out
     387             :          * values which process to |undefined|, and 4) stringifying all values
     388             :          * which pass the filter.
     389             :          */
     390         215 :         id = propertyList[i];
     391         430 :         RootedValue outputValue(cx);
     392             : #ifdef DEBUG
     393         215 :         if (scx->maybeSafely) {
     394           0 :             RootedNativeObject nativeObj(cx, &obj->as<NativeObject>());
     395           0 :             Rooted<PropertyResult> prop(cx);
     396           0 :             NativeLookupOwnPropertyNoResolve(cx, nativeObj, id, &prop);
     397           0 :             MOZ_ASSERT(prop && prop.isNativeProperty() && prop.shape()->isDataDescriptor());
     398             :         }
     399             : #endif // DEBUG
     400         215 :         if (!GetProperty(cx, obj, obj, id, &outputValue))
     401           0 :             return false;
     402         215 :         if (!PreprocessValue(cx, obj, HandleId(id), &outputValue, scx))
     403           0 :             return false;
     404         215 :         if (IsFilteredValue(outputValue))
     405           0 :             continue;
     406             : 
     407             :         /* Output a comma unless this is the first member to write. */
     408         215 :         if (wroteMember && !scx->sb.append(','))
     409           0 :             return false;
     410         215 :         wroteMember = true;
     411             : 
     412         215 :         if (!WriteIndent(cx, scx, scx->depth))
     413           0 :             return false;
     414             : 
     415         215 :         JSString* s = IdToString(cx, id);
     416         215 :         if (!s)
     417           0 :             return false;
     418             : 
     419         645 :         if (!Quote(cx, scx->sb, s) ||
     420         430 :             !scx->sb.append(':') ||
     421         709 :             !(scx->gap.empty() || scx->sb.append(' ')) ||
     422         215 :             !Str(cx, outputValue, scx))
     423             :         {
     424           0 :             return false;
     425             :         }
     426             :     }
     427             : 
     428          48 :     if (wroteMember && !WriteIndent(cx, scx, scx->depth - 1))
     429           0 :         return false;
     430             : 
     431          48 :     return scx->sb.append('}');
     432             : }
     433             : 
     434             : /* ES5 15.12.3 JA. */
     435             : static bool
     436          28 : JA(JSContext* cx, HandleObject obj, StringifyContext* scx)
     437             : {
     438             :     /*
     439             :      * This method implements the JA algorithm in ES5 15.12.3, but:
     440             :      *
     441             :      *   * The algorithm is somewhat reformulated to allow the final string to
     442             :      *     be streamed into a single buffer, rather than be created and copied
     443             :      *     into place incrementally as the ES5 algorithm specifies it.  This
     444             :      *     requires moving portions of the Str call in 8a into this algorithm
     445             :      *     (and in JO as well).
     446             :      */
     447             : 
     448             :     /* Steps 1-2, 11. */
     449          56 :     CycleDetector detect(scx, obj);
     450          28 :     if (!detect.foundCycle(cx))
     451           0 :         return false;
     452             : 
     453          28 :     if (!scx->sb.append('['))
     454           0 :         return false;
     455             : 
     456             :     /* Step 6. */
     457             :     uint32_t length;
     458          28 :     if (!GetLengthProperty(cx, obj, &length))
     459           0 :         return false;
     460             : 
     461             :     /* Steps 7-10. */
     462          28 :     if (length != 0) {
     463             :         /* Steps 4, 10b(i). */
     464          23 :         if (!WriteIndent(cx, scx, scx->depth))
     465           0 :             return false;
     466             : 
     467             :         /* Steps 7-10. */
     468          46 :         RootedValue outputValue(cx);
     469         108 :         for (uint32_t i = 0; i < length; i++) {
     470          85 :             if (!CheckForInterrupt(cx))
     471           0 :                 return false;
     472             : 
     473             :             /*
     474             :              * Steps 8a-8c.  Again note how the call to the spec's Str method
     475             :              * is broken up into getting the property, running it past toJSON
     476             :              * and the replacer and maybe unboxing, and interpreting some
     477             :              * values as |null| in separate steps.
     478             :              */
     479             : #ifdef DEBUG
     480          85 :             if (scx->maybeSafely) {
     481             :                 /*
     482             :                  * Trying to do a JS_AlreadyHasOwnElement runs the risk of
     483             :                  * hitting OOM on jsid creation.  Let's just assert sanity for
     484             :                  * small enough indices.
     485             :                  */
     486           0 :                 MOZ_ASSERT(obj->is<ArrayObject>());
     487           0 :                 MOZ_ASSERT(obj->is<NativeObject>());
     488           0 :                 RootedNativeObject nativeObj(cx, &obj->as<NativeObject>());
     489           0 :                 if (i <= JSID_INT_MAX) {
     490           0 :                     MOZ_ASSERT(nativeObj->containsDenseElement(i) != nativeObj->isIndexed(),
     491             :                                "the array must either be small enough to remain "
     492             :                                "fully dense (and otherwise un-indexed), *or* "
     493             :                                "all its initially-dense elements were sparsified "
     494             :                                "and the object is indexed");
     495             :                 } else {
     496           0 :                     MOZ_ASSERT(obj->isIndexed());
     497             :                 }
     498             :             }
     499             : #endif
     500          85 :             if (!GetElement(cx, obj, i, &outputValue))
     501           0 :                 return false;
     502          85 :             if (!PreprocessValue(cx, obj, i, &outputValue, scx))
     503           0 :                 return false;
     504          85 :             if (IsFilteredValue(outputValue)) {
     505           0 :                 if (!scx->sb.append("null"))
     506           0 :                     return false;
     507             :             } else {
     508          85 :                 if (!Str(cx, outputValue, scx))
     509           0 :                     return false;
     510             :             }
     511             : 
     512             :             /* Steps 3, 4, 10b(i). */
     513          85 :             if (i < length - 1) {
     514          62 :                 if (!scx->sb.append(','))
     515           0 :                     return false;
     516          62 :                 if (!WriteIndent(cx, scx, scx->depth))
     517           0 :                     return false;
     518             :             }
     519             :         }
     520             : 
     521             :         /* Step 10(b)(iii). */
     522          23 :         if (!WriteIndent(cx, scx, scx->depth - 1))
     523           0 :             return false;
     524             :     }
     525             : 
     526          28 :     return scx->sb.append(']');
     527             : }
     528             : 
     529             : static bool
     530         315 : Str(JSContext* cx, const Value& v, StringifyContext* scx)
     531             : {
     532             :     /* Step 11 must be handled by the caller. */
     533         315 :     MOZ_ASSERT(!IsFilteredValue(v));
     534             : 
     535         315 :     if (!CheckRecursionLimit(cx))
     536           0 :         return false;
     537             : 
     538             :     /*
     539             :      * This method implements the Str algorithm in ES5 15.12.3, but:
     540             :      *
     541             :      *   * We move property retrieval (step 1) into callers to stream the
     542             :      *     stringification process and avoid constantly copying strings.
     543             :      *   * We move the preprocessing in steps 2-4 into a helper function to
     544             :      *     allow both JO and JA to use this method.  While JA could use it
     545             :      *     without this move, JO must omit any |undefined|-valued property per
     546             :      *     so it can't stream out a value using the Str method exactly as
     547             :      *     defined by ES5.
     548             :      *   * We move step 11 into callers, again to ease streaming.
     549             :      */
     550             : 
     551             :     /* Step 8. */
     552         315 :     if (v.isString())
     553         149 :         return Quote(cx, scx->sb, v.toString());
     554             : 
     555             :     /* Step 5. */
     556         166 :     if (v.isNull())
     557          21 :         return scx->sb.append("null");
     558             : 
     559             :     /* Steps 6-7. */
     560         145 :     if (v.isBoolean())
     561          49 :         return v.toBoolean() ? scx->sb.append("true") : scx->sb.append("false");
     562             : 
     563             :     /* Step 9. */
     564          96 :     if (v.isNumber()) {
     565          20 :         if (v.isDouble()) {
     566           0 :             if (!IsFinite(v.toDouble())) {
     567           0 :                 MOZ_ASSERT(!scx->maybeSafely,
     568             :                            "input JS::ToJSONMaybeSafely must not include "
     569             :                            "reachable non-finite numbers");
     570           0 :                 return scx->sb.append("null");
     571             :             }
     572             :         }
     573             : 
     574          20 :         return NumberValueToStringBuffer(cx, v, scx->sb);
     575             :     }
     576             : 
     577             :     /* Step 10. */
     578          76 :     MOZ_ASSERT(v.isObject());
     579         152 :     RootedObject obj(cx, &v.toObject());
     580             : 
     581          76 :     MOZ_ASSERT(!scx->maybeSafely || obj->is<PlainObject>() || obj->is<ArrayObject>(),
     582             :                "input to JS::ToJSONMaybeSafely must not include reachable "
     583             :                "objects that are neither arrays nor plain objects");
     584             : 
     585          76 :     scx->depth++;
     586         228 :     auto dec = mozilla::MakeScopeExit([&] { scx->depth--; });
     587             : 
     588             :     bool isArray;
     589          76 :     if (!IsArray(cx, obj, &isArray))
     590           0 :         return false;
     591             : 
     592          76 :     return isArray ? JA(cx, obj, scx) : JO(cx, obj, scx);
     593             : }
     594             : 
     595             : /* ES6 24.3.2. */
     596             : bool
     597          15 : js::Stringify(JSContext* cx, MutableHandleValue vp, JSObject* replacer_, const Value& space_,
     598             :               StringBuffer& sb, StringifyBehavior stringifyBehavior)
     599             : {
     600          30 :     RootedObject replacer(cx, replacer_);
     601          30 :     RootedValue space(cx, space_);
     602             : 
     603          15 :     MOZ_ASSERT_IF(stringifyBehavior == StringifyBehavior::RestrictedSafe, space.isNull());
     604          15 :     MOZ_ASSERT_IF(stringifyBehavior == StringifyBehavior::RestrictedSafe, vp.isObject());
     605             :     /**
     606             :      * This uses MOZ_ASSERT, since it's actually asserting something jsapi
     607             :      * consumers could get wrong, so needs a better error message.
     608             :      */
     609          15 :     MOZ_ASSERT(stringifyBehavior == StringifyBehavior::Normal ||
     610             :                vp.toObject().is<PlainObject>() || vp.toObject().is<ArrayObject>(),
     611             :                "input to JS::ToJSONMaybeSafely must be a plain object or array");
     612             : 
     613             :     /* Step 4. */
     614          30 :     AutoIdVector propertyList(cx);
     615          15 :     if (replacer) {
     616             :         bool isArray;
     617           3 :         if (replacer->isCallable()) {
     618             :             /* Step 4a(i): use replacer to transform values.  */
     619           0 :         } else if (!IsArray(cx, replacer, &isArray)) {
     620           0 :             return false;
     621           0 :         } else if (isArray) {
     622             :             /* Step 4b(iii). */
     623             : 
     624             :             /* Step 4b(iii)(2-3). */
     625             :             uint32_t len;
     626           0 :             if (!GetLengthProperty(cx, replacer, &len))
     627           0 :                 return false;
     628             : 
     629             :             // Cap the initial size to a moderately small value.  This avoids
     630             :             // ridiculous over-allocation if an array with bogusly-huge length
     631             :             // is passed in.  If we end up having to add elements past this
     632             :             // size, the set will naturally resize to accommodate them.
     633           0 :             const uint32_t MaxInitialSize = 32;
     634           0 :             Rooted<GCHashSet<jsid>> idSet(cx, GCHashSet<jsid>(cx));
     635           0 :             if (!idSet.init(Min(len, MaxInitialSize)))
     636           0 :                 return false;
     637             : 
     638             :             /* Step 4b(iii)(4). */
     639           0 :             uint32_t k = 0;
     640             : 
     641             :             /* Step 4b(iii)(5). */
     642           0 :             RootedValue item(cx);
     643           0 :             for (; k < len; k++) {
     644           0 :                 if (!CheckForInterrupt(cx))
     645           0 :                     return false;
     646             : 
     647             :                 /* Step 4b(iii)(5)(a-b). */
     648           0 :                 if (!GetElement(cx, replacer, k, &item))
     649           0 :                     return false;
     650             : 
     651           0 :                 RootedId id(cx);
     652             : 
     653             :                 /* Step 4b(iii)(5)(c-f). */
     654           0 :                 if (item.isNumber()) {
     655             :                     /* Step 4b(iii)(5)(e). */
     656             :                     int32_t n;
     657           0 :                     if (ValueFitsInInt32(item, &n) && INT_FITS_IN_JSID(n)) {
     658           0 :                         id = INT_TO_JSID(n);
     659             :                     } else {
     660           0 :                         if (!ValueToId<CanGC>(cx, item, &id))
     661           0 :                             return false;
     662             :                     }
     663             :                 } else {
     664           0 :                     bool shouldAdd = item.isString();
     665           0 :                     if (!shouldAdd) {
     666             :                         ESClass cls;
     667           0 :                         if (!GetClassOfValue(cx, item, &cls))
     668           0 :                             return false;
     669             : 
     670           0 :                         shouldAdd = cls == ESClass::String || cls == ESClass::Number;
     671             :                     }
     672             : 
     673           0 :                     if (shouldAdd) {
     674             :                         /* Step 4b(iii)(5)(f). */
     675           0 :                         if (!ValueToId<CanGC>(cx, item, &id))
     676           0 :                             return false;
     677             :                     } else {
     678             :                         /* Step 4b(iii)(5)(g). */
     679           0 :                         continue;
     680             :                     }
     681             :                 }
     682             : 
     683             :                 /* Step 4b(iii)(5)(g). */
     684           0 :                 auto p = idSet.lookupForAdd(id);
     685           0 :                 if (!p) {
     686             :                     /* Step 4b(iii)(5)(g)(i). */
     687           0 :                     if (!idSet.add(p, id) || !propertyList.append(id))
     688           0 :                         return false;
     689             :                 }
     690             :             }
     691             :         } else {
     692           0 :             replacer = nullptr;
     693             :         }
     694             :     }
     695             : 
     696             :     /* Step 5. */
     697          15 :     if (space.isObject()) {
     698           0 :         RootedObject spaceObj(cx, &space.toObject());
     699             : 
     700             :         ESClass cls;
     701           0 :         if (!GetBuiltinClass(cx, spaceObj, &cls))
     702           0 :             return false;
     703             : 
     704           0 :         if (cls == ESClass::Number) {
     705             :             double d;
     706           0 :             if (!ToNumber(cx, space, &d))
     707           0 :                 return false;
     708           0 :             space = NumberValue(d);
     709           0 :         } else if (cls == ESClass::String) {
     710           0 :             JSString* str = ToStringSlow<CanGC>(cx, space);
     711           0 :             if (!str)
     712           0 :                 return false;
     713           0 :             space = StringValue(str);
     714             :         }
     715             :     }
     716             : 
     717          30 :     StringBuffer gap(cx);
     718             : 
     719          15 :     if (space.isNumber()) {
     720             :         /* Step 6. */
     721             :         double d;
     722           3 :         MOZ_ALWAYS_TRUE(ToInteger(cx, space, &d));
     723           3 :         d = Min(10.0, d);
     724           3 :         if (d >= 1 && !gap.appendN(' ', uint32_t(d)))
     725           0 :             return false;
     726          12 :     } else if (space.isString()) {
     727             :         /* Step 7. */
     728           0 :         JSLinearString* str = space.toString()->ensureLinear(cx);
     729           0 :         if (!str)
     730           0 :             return false;
     731           0 :         size_t len = Min(size_t(10), str->length());
     732           0 :         if (!gap.appendSubstring(str, 0, len))
     733           0 :             return false;
     734             :     } else {
     735             :         /* Step 8. */
     736          12 :         MOZ_ASSERT(gap.empty());
     737             :     }
     738             : 
     739             :     /* Step 9. */
     740          30 :     RootedPlainObject wrapper(cx, NewBuiltinClassInstance<PlainObject>(cx));
     741          15 :     if (!wrapper)
     742           0 :         return false;
     743             : 
     744             :     /* Steps 10-11. */
     745          30 :     RootedId emptyId(cx, NameToId(cx->names().empty));
     746          15 :     if (!NativeDefineProperty(cx, wrapper, emptyId, vp, nullptr, nullptr, JSPROP_ENUMERATE))
     747           0 :         return false;
     748             : 
     749             :     /* Step 12. */
     750             :     StringifyContext scx(cx, sb, gap, replacer, propertyList,
     751          30 :                          stringifyBehavior == StringifyBehavior::RestrictedSafe);
     752          15 :     if (!PreprocessValue(cx, wrapper, HandleId(emptyId), vp, &scx))
     753           0 :         return false;
     754          15 :     if (IsFilteredValue(vp))
     755           0 :         return true;
     756             : 
     757          15 :     return Str(cx, vp, &scx);
     758             : }
     759             : 
     760             : /* ES5 15.12.2 Walk. */
     761             : static bool
     762           0 : Walk(JSContext* cx, HandleObject holder, HandleId name, HandleValue reviver, MutableHandleValue vp)
     763             : {
     764           0 :     if (!CheckRecursionLimit(cx))
     765           0 :         return false;
     766             : 
     767             :     /* Step 1. */
     768           0 :     RootedValue val(cx);
     769           0 :     if (!GetProperty(cx, holder, holder, name, &val))
     770           0 :         return false;
     771             : 
     772             :     /* Step 2. */
     773           0 :     if (val.isObject()) {
     774           0 :         RootedObject obj(cx, &val.toObject());
     775             : 
     776             :         bool isArray;
     777           0 :         if (!IsArray(cx, obj, &isArray))
     778           0 :             return false;
     779             : 
     780           0 :         if (isArray) {
     781             :             /* Step 2a(ii). */
     782             :             uint32_t length;
     783           0 :             if (!GetLengthProperty(cx, obj, &length))
     784           0 :                 return false;
     785             : 
     786             :             /* Step 2a(i), 2a(iii-iv). */
     787           0 :             RootedId id(cx);
     788           0 :             RootedValue newElement(cx);
     789           0 :             for (uint32_t i = 0; i < length; i++) {
     790           0 :                 if (!CheckForInterrupt(cx))
     791           0 :                     return false;
     792             : 
     793           0 :                 if (!IndexToId(cx, i, &id))
     794           0 :                     return false;
     795             : 
     796             :                 /* Step 2a(iii)(1). */
     797           0 :                 if (!Walk(cx, obj, id, reviver, &newElement))
     798           0 :                     return false;
     799             : 
     800           0 :                 ObjectOpResult ignored;
     801           0 :                 if (newElement.isUndefined()) {
     802             :                     /* Step 2a(iii)(2). The spec deliberately ignores strict failure. */
     803           0 :                     if (!DeleteProperty(cx, obj, id, ignored))
     804           0 :                         return false;
     805             :                 } else {
     806             :                     /* Step 2a(iii)(3). The spec deliberately ignores strict failure. */
     807           0 :                     Rooted<PropertyDescriptor> desc(cx);
     808           0 :                     desc.setDataDescriptor(newElement, JSPROP_ENUMERATE);
     809           0 :                     if (!DefineProperty(cx, obj, id, desc, ignored))
     810           0 :                         return false;
     811             :                 }
     812             :             }
     813             :         } else {
     814             :             /* Step 2b(i). */
     815           0 :             AutoIdVector keys(cx);
     816           0 :             if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY, &keys))
     817           0 :                 return false;
     818             : 
     819             :             /* Step 2b(ii). */
     820           0 :             RootedId id(cx);
     821           0 :             RootedValue newElement(cx);
     822           0 :             for (size_t i = 0, len = keys.length(); i < len; i++) {
     823           0 :                 if (!CheckForInterrupt(cx))
     824           0 :                     return false;
     825             : 
     826             :                 /* Step 2b(ii)(1). */
     827           0 :                 id = keys[i];
     828           0 :                 if (!Walk(cx, obj, id, reviver, &newElement))
     829           0 :                     return false;
     830             : 
     831           0 :                 ObjectOpResult ignored;
     832           0 :                 if (newElement.isUndefined()) {
     833             :                     /* Step 2b(ii)(2). The spec deliberately ignores strict failure. */
     834           0 :                     if (!DeleteProperty(cx, obj, id, ignored))
     835           0 :                         return false;
     836             :                 } else {
     837             :                     /* Step 2b(ii)(3). The spec deliberately ignores strict failure. */
     838           0 :                     Rooted<PropertyDescriptor> desc(cx);
     839           0 :                     desc.setDataDescriptor(newElement, JSPROP_ENUMERATE);
     840           0 :                     if (!DefineProperty(cx, obj, id, desc, ignored))
     841           0 :                         return false;
     842             :                 }
     843             :             }
     844             :         }
     845             :     }
     846             : 
     847             :     /* Step 3. */
     848           0 :     RootedString key(cx, IdToString(cx, name));
     849           0 :     if (!key)
     850           0 :         return false;
     851             : 
     852           0 :     RootedValue keyVal(cx, StringValue(key));
     853           0 :     return js::Call(cx, reviver, holder, keyVal, val, vp);
     854             : }
     855             : 
     856             : static bool
     857           0 : Revive(JSContext* cx, HandleValue reviver, MutableHandleValue vp)
     858             : {
     859           0 :     RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
     860           0 :     if (!obj)
     861           0 :         return false;
     862             : 
     863           0 :     if (!DefineProperty(cx, obj, cx->names().empty, vp))
     864           0 :         return false;
     865             : 
     866           0 :     Rooted<jsid> id(cx, NameToId(cx->names().empty));
     867           0 :     return Walk(cx, obj, id, reviver, vp);
     868             : }
     869             : 
     870             : template <typename CharT>
     871             : bool
     872          10 : js::ParseJSONWithReviver(JSContext* cx, const mozilla::Range<const CharT> chars, HandleValue reviver,
     873             :                          MutableHandleValue vp)
     874             : {
     875             :     /* 15.12.2 steps 2-3. */
     876          20 :     Rooted<JSONParser<CharT>> parser(cx, JSONParser<CharT>(cx, chars));
     877          10 :     if (!parser.parse(vp))
     878           0 :         return false;
     879             : 
     880             :     /* 15.12.2 steps 4-5. */
     881          10 :     if (IsCallable(reviver))
     882           0 :         return Revive(cx, reviver, vp);
     883          10 :     return true;
     884             : }
     885             : 
     886             : template bool
     887             : js::ParseJSONWithReviver(JSContext* cx, const mozilla::Range<const Latin1Char> chars,
     888             :                          HandleValue reviver, MutableHandleValue vp);
     889             : 
     890             : template bool
     891             : js::ParseJSONWithReviver(JSContext* cx, const mozilla::Range<const char16_t> chars, HandleValue reviver,
     892             :                          MutableHandleValue vp);
     893             : 
     894             : #if JS_HAS_TOSOURCE
     895             : static bool
     896           0 : json_toSource(JSContext* cx, unsigned argc, Value* vp)
     897             : {
     898           0 :     CallArgs args = CallArgsFromVp(argc, vp);
     899           0 :     args.rval().setString(cx->names().JSON);
     900           0 :     return true;
     901             : }
     902             : #endif
     903             : 
     904             : /* ES5 15.12.2. */
     905             : static bool
     906           5 : json_parse(JSContext* cx, unsigned argc, Value* vp)
     907             : {
     908           5 :     CallArgs args = CallArgsFromVp(argc, vp);
     909             : 
     910             :     /* Step 1. */
     911           5 :     JSString* str = (args.length() >= 1)
     912          15 :                     ? ToString<CanGC>(cx, args[0])
     913           5 :                     : cx->names().undefined;
     914           5 :     if (!str)
     915           0 :         return false;
     916             : 
     917           5 :     JSLinearString* linear = str->ensureLinear(cx);
     918           5 :     if (!linear)
     919           0 :         return false;
     920             : 
     921          10 :     AutoStableStringChars linearChars(cx);
     922           5 :     if (!linearChars.init(cx, linear))
     923           0 :         return false;
     924             : 
     925           5 :     HandleValue reviver = args.get(1);
     926             : 
     927             :     /* Steps 2-5. */
     928           5 :     return linearChars.isLatin1()
     929           9 :            ? ParseJSONWithReviver(cx, linearChars.latin1Range(), reviver, args.rval())
     930           9 :            : ParseJSONWithReviver(cx, linearChars.twoByteRange(), reviver, args.rval());
     931             : }
     932             : 
     933             : /* ES6 24.3.2. */
     934             : bool
     935          15 : json_stringify(JSContext* cx, unsigned argc, Value* vp)
     936             : {
     937          15 :     CallArgs args = CallArgsFromVp(argc, vp);
     938             : 
     939          30 :     RootedObject replacer(cx, args.get(1).isObject() ? &args[1].toObject() : nullptr);
     940          30 :     RootedValue value(cx, args.get(0));
     941          30 :     RootedValue space(cx, args.get(2));
     942             : 
     943          30 :     StringBuffer sb(cx);
     944          15 :     if (!Stringify(cx, &value, replacer, space, sb, StringifyBehavior::Normal))
     945           0 :         return false;
     946             : 
     947             :     // XXX This can never happen to nsJSON.cpp, but the JSON object
     948             :     // needs to support returning undefined. So this is a little awkward
     949             :     // for the API, because we want to support streaming writers.
     950          15 :     if (!sb.empty()) {
     951          15 :         JSString* str = sb.finishString();
     952          15 :         if (!str)
     953           0 :             return false;
     954          15 :         args.rval().setString(str);
     955             :     } else {
     956           0 :         args.rval().setUndefined();
     957             :     }
     958             : 
     959          15 :     return true;
     960             : }
     961             : 
     962             : static const JSFunctionSpec json_static_methods[] = {
     963             : #if JS_HAS_TOSOURCE
     964             :     JS_FN(js_toSource_str,  json_toSource,      0, 0),
     965             : #endif
     966             :     JS_FN("parse",          json_parse,         2, 0),
     967             :     JS_FN("stringify",      json_stringify,     3, 0),
     968             :     JS_FS_END
     969             : };
     970             : 
     971             : JSObject*
     972          17 : js::InitJSONClass(JSContext* cx, HandleObject obj)
     973             : {
     974          17 :     Handle<GlobalObject*> global = obj.as<GlobalObject>();
     975             : 
     976          34 :     RootedObject proto(cx, GlobalObject::getOrCreateObjectPrototype(cx, global));
     977          17 :     if (!proto)
     978           0 :         return nullptr;
     979          34 :     RootedObject JSON(cx, NewObjectWithGivenProto(cx, &JSONClass, proto, SingletonObject));
     980          17 :     if (!JSON)
     981           0 :         return nullptr;
     982             : 
     983          17 :     if (!JS_DefineProperty(cx, global, js_JSON_str, JSON, JSPROP_RESOLVING,
     984             :                            JS_STUBGETTER, JS_STUBSETTER))
     985             :     {
     986           0 :         return nullptr;
     987             :     }
     988             : 
     989          17 :     if (!JS_DefineFunctions(cx, JSON, json_static_methods))
     990           0 :         return nullptr;
     991             : 
     992          17 :     if (!DefineToStringTag(cx, JSON, cx->names().JSON))
     993           0 :         return nullptr;
     994             : 
     995          17 :     global->setConstructor(JSProto_JSON, ObjectValue(*JSON));
     996             : 
     997          17 :     return JSON;
     998             : }

Generated by: LCOV version 1.13