1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/js/src/json.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,892 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- 1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99: 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "json.h" 1.11 + 1.12 +#include "mozilla/FloatingPoint.h" 1.13 + 1.14 +#include "jsarray.h" 1.15 +#include "jsatom.h" 1.16 +#include "jscntxt.h" 1.17 +#include "jsnum.h" 1.18 +#include "jsobj.h" 1.19 +#include "jsonparser.h" 1.20 +#include "jsstr.h" 1.21 +#include "jstypes.h" 1.22 +#include "jsutil.h" 1.23 + 1.24 +#include "vm/Interpreter.h" 1.25 +#include "vm/StringBuffer.h" 1.26 + 1.27 +#include "jsatominlines.h" 1.28 +#include "jsboolinlines.h" 1.29 +#include "jsobjinlines.h" 1.30 + 1.31 +using namespace js; 1.32 +using namespace js::gc; 1.33 +using namespace js::types; 1.34 + 1.35 +using mozilla::IsFinite; 1.36 +using mozilla::Maybe; 1.37 + 1.38 +const Class js::JSONClass = { 1.39 + js_JSON_str, 1.40 + JSCLASS_HAS_CACHED_PROTO(JSProto_JSON), 1.41 + JS_PropertyStub, /* addProperty */ 1.42 + JS_DeletePropertyStub, /* delProperty */ 1.43 + JS_PropertyStub, /* getProperty */ 1.44 + JS_StrictPropertyStub, /* setProperty */ 1.45 + JS_EnumerateStub, 1.46 + JS_ResolveStub, 1.47 + JS_ConvertStub 1.48 +}; 1.49 + 1.50 +static inline bool IsQuoteSpecialCharacter(jschar c) 1.51 +{ 1.52 + JS_STATIC_ASSERT('\b' < ' '); 1.53 + JS_STATIC_ASSERT('\f' < ' '); 1.54 + JS_STATIC_ASSERT('\n' < ' '); 1.55 + JS_STATIC_ASSERT('\r' < ' '); 1.56 + JS_STATIC_ASSERT('\t' < ' '); 1.57 + return c == '"' || c == '\\' || c < ' '; 1.58 +} 1.59 + 1.60 +/* ES5 15.12.3 Quote. */ 1.61 +static bool 1.62 +Quote(JSContext *cx, StringBuffer &sb, JSString *str) 1.63 +{ 1.64 + JS::Anchor<JSString *> anchor(str); 1.65 + size_t len = str->length(); 1.66 + const jschar *buf = str->getChars(cx); 1.67 + if (!buf) 1.68 + return false; 1.69 + 1.70 + /* Step 1. */ 1.71 + if (!sb.append('"')) 1.72 + return false; 1.73 + 1.74 + /* Step 2. */ 1.75 + for (size_t i = 0; i < len; ++i) { 1.76 + /* Batch-append maximal character sequences containing no escapes. */ 1.77 + size_t mark = i; 1.78 + do { 1.79 + if (IsQuoteSpecialCharacter(buf[i])) 1.80 + break; 1.81 + } while (++i < len); 1.82 + if (i > mark) { 1.83 + if (!sb.append(&buf[mark], i - mark)) 1.84 + return false; 1.85 + if (i == len) 1.86 + break; 1.87 + } 1.88 + 1.89 + jschar c = buf[i]; 1.90 + if (c == '"' || c == '\\') { 1.91 + if (!sb.append('\\') || !sb.append(c)) 1.92 + return false; 1.93 + } else if (c == '\b' || c == '\f' || c == '\n' || c == '\r' || c == '\t') { 1.94 + jschar abbrev = (c == '\b') 1.95 + ? 'b' 1.96 + : (c == '\f') 1.97 + ? 'f' 1.98 + : (c == '\n') 1.99 + ? 'n' 1.100 + : (c == '\r') 1.101 + ? 'r' 1.102 + : 't'; 1.103 + if (!sb.append('\\') || !sb.append(abbrev)) 1.104 + return false; 1.105 + } else { 1.106 + JS_ASSERT(c < ' '); 1.107 + if (!sb.append("\\u00")) 1.108 + return false; 1.109 + JS_ASSERT((c >> 4) < 10); 1.110 + uint8_t x = c >> 4, y = c % 16; 1.111 + if (!sb.append('0' + x) || !sb.append(y < 10 ? '0' + y : 'a' + (y - 10))) 1.112 + return false; 1.113 + } 1.114 + } 1.115 + 1.116 + /* Steps 3-4. */ 1.117 + return sb.append('"'); 1.118 +} 1.119 + 1.120 +namespace { 1.121 + 1.122 +class StringifyContext 1.123 +{ 1.124 + public: 1.125 + StringifyContext(JSContext *cx, StringBuffer &sb, const StringBuffer &gap, 1.126 + HandleObject replacer, const AutoIdVector &propertyList) 1.127 + : sb(sb), 1.128 + gap(gap), 1.129 + replacer(cx, replacer), 1.130 + propertyList(propertyList), 1.131 + depth(0) 1.132 + {} 1.133 + 1.134 + StringBuffer &sb; 1.135 + const StringBuffer ⪆ 1.136 + RootedObject replacer; 1.137 + const AutoIdVector &propertyList; 1.138 + uint32_t depth; 1.139 +}; 1.140 + 1.141 +} /* anonymous namespace */ 1.142 + 1.143 +static bool Str(JSContext *cx, const Value &v, StringifyContext *scx); 1.144 + 1.145 +static bool 1.146 +WriteIndent(JSContext *cx, StringifyContext *scx, uint32_t limit) 1.147 +{ 1.148 + if (!scx->gap.empty()) { 1.149 + if (!scx->sb.append('\n')) 1.150 + return false; 1.151 + for (uint32_t i = 0; i < limit; i++) { 1.152 + if (!scx->sb.append(scx->gap.begin(), scx->gap.end())) 1.153 + return false; 1.154 + } 1.155 + } 1.156 + 1.157 + return true; 1.158 +} 1.159 + 1.160 +namespace { 1.161 + 1.162 +template<typename KeyType> 1.163 +class KeyStringifier { 1.164 +}; 1.165 + 1.166 +template<> 1.167 +class KeyStringifier<uint32_t> { 1.168 + public: 1.169 + static JSString *toString(JSContext *cx, uint32_t index) { 1.170 + return IndexToString(cx, index); 1.171 + } 1.172 +}; 1.173 + 1.174 +template<> 1.175 +class KeyStringifier<HandleId> { 1.176 + public: 1.177 + static JSString *toString(JSContext *cx, HandleId id) { 1.178 + return IdToString(cx, id); 1.179 + } 1.180 +}; 1.181 + 1.182 +} /* anonymous namespace */ 1.183 + 1.184 +/* 1.185 + * ES5 15.12.3 Str, steps 2-4, extracted to enable preprocessing of property 1.186 + * values when stringifying objects in JO. 1.187 + */ 1.188 +template<typename KeyType> 1.189 +static bool 1.190 +PreprocessValue(JSContext *cx, HandleObject holder, KeyType key, MutableHandleValue vp, StringifyContext *scx) 1.191 +{ 1.192 + RootedString keyStr(cx); 1.193 + 1.194 + /* Step 2. */ 1.195 + if (vp.isObject()) { 1.196 + RootedValue toJSON(cx); 1.197 + RootedObject obj(cx, &vp.toObject()); 1.198 + if (!JSObject::getProperty(cx, obj, obj, cx->names().toJSON, &toJSON)) 1.199 + return false; 1.200 + 1.201 + if (js_IsCallable(toJSON)) { 1.202 + keyStr = KeyStringifier<KeyType>::toString(cx, key); 1.203 + if (!keyStr) 1.204 + return false; 1.205 + 1.206 + InvokeArgs args(cx); 1.207 + if (!args.init(1)) 1.208 + return false; 1.209 + 1.210 + args.setCallee(toJSON); 1.211 + args.setThis(vp); 1.212 + args[0].setString(keyStr); 1.213 + 1.214 + if (!Invoke(cx, args)) 1.215 + return false; 1.216 + vp.set(args.rval()); 1.217 + } 1.218 + } 1.219 + 1.220 + /* Step 3. */ 1.221 + if (scx->replacer && scx->replacer->isCallable()) { 1.222 + if (!keyStr) { 1.223 + keyStr = KeyStringifier<KeyType>::toString(cx, key); 1.224 + if (!keyStr) 1.225 + return false; 1.226 + } 1.227 + 1.228 + InvokeArgs args(cx); 1.229 + if (!args.init(2)) 1.230 + return false; 1.231 + 1.232 + args.setCallee(ObjectValue(*scx->replacer)); 1.233 + args.setThis(ObjectValue(*holder)); 1.234 + args[0].setString(keyStr); 1.235 + args[1].set(vp); 1.236 + 1.237 + if (!Invoke(cx, args)) 1.238 + return false; 1.239 + vp.set(args.rval()); 1.240 + } 1.241 + 1.242 + /* Step 4. */ 1.243 + if (vp.get().isObject()) { 1.244 + RootedObject obj(cx, &vp.get().toObject()); 1.245 + if (ObjectClassIs(obj, ESClass_Number, cx)) { 1.246 + double d; 1.247 + if (!ToNumber(cx, vp, &d)) 1.248 + return false; 1.249 + vp.set(NumberValue(d)); 1.250 + } else if (ObjectClassIs(obj, ESClass_String, cx)) { 1.251 + JSString *str = ToStringSlow<CanGC>(cx, vp); 1.252 + if (!str) 1.253 + return false; 1.254 + vp.set(StringValue(str)); 1.255 + } else if (ObjectClassIs(obj, ESClass_Boolean, cx)) { 1.256 + vp.setBoolean(BooleanGetPrimitiveValue(obj)); 1.257 + } 1.258 + } 1.259 + 1.260 + return true; 1.261 +} 1.262 + 1.263 +/* 1.264 + * Determines whether a value which has passed by ES5 150.2.3 Str steps 1-4's 1.265 + * gauntlet will result in Str returning |undefined|. This function is used to 1.266 + * properly omit properties resulting in such values when stringifying objects, 1.267 + * while properly stringifying such properties as null when they're encountered 1.268 + * in arrays. 1.269 + */ 1.270 +static inline bool 1.271 +IsFilteredValue(const Value &v) 1.272 +{ 1.273 + return v.isUndefined() || js_IsCallable(v); 1.274 +} 1.275 + 1.276 +/* ES5 15.12.3 JO. */ 1.277 +static bool 1.278 +JO(JSContext *cx, HandleObject obj, StringifyContext *scx) 1.279 +{ 1.280 + /* 1.281 + * This method implements the JO algorithm in ES5 15.12.3, but: 1.282 + * 1.283 + * * The algorithm is somewhat reformulated to allow the final string to 1.284 + * be streamed into a single buffer, rather than be created and copied 1.285 + * into place incrementally as the ES5 algorithm specifies it. This 1.286 + * requires moving portions of the Str call in 8a into this algorithm 1.287 + * (and in JA as well). 1.288 + */ 1.289 + 1.290 + /* Steps 1-2, 11. */ 1.291 + AutoCycleDetector detect(cx, obj); 1.292 + if (!detect.init()) 1.293 + return false; 1.294 + if (detect.foundCycle()) { 1.295 + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CYCLIC_VALUE, js_object_str); 1.296 + return false; 1.297 + } 1.298 + 1.299 + if (!scx->sb.append('{')) 1.300 + return false; 1.301 + 1.302 + /* Steps 5-7. */ 1.303 + Maybe<AutoIdVector> ids; 1.304 + const AutoIdVector *props; 1.305 + if (scx->replacer && !scx->replacer->isCallable()) { 1.306 + JS_ASSERT(JS_IsArrayObject(cx, scx->replacer)); 1.307 + props = &scx->propertyList; 1.308 + } else { 1.309 + JS_ASSERT_IF(scx->replacer, scx->propertyList.length() == 0); 1.310 + ids.construct(cx); 1.311 + if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, ids.addr())) 1.312 + return false; 1.313 + props = ids.addr(); 1.314 + } 1.315 + 1.316 + /* My kingdom for not-quite-initialized-from-the-start references. */ 1.317 + const AutoIdVector &propertyList = *props; 1.318 + 1.319 + /* Steps 8-10, 13. */ 1.320 + bool wroteMember = false; 1.321 + RootedId id(cx); 1.322 + for (size_t i = 0, len = propertyList.length(); i < len; i++) { 1.323 + /* 1.324 + * Steps 8a-8b. Note that the call to Str is broken up into 1) getting 1.325 + * the property; 2) processing for toJSON, calling the replacer, and 1.326 + * handling boxed Number/String/Boolean objects; 3) filtering out 1.327 + * values which process to |undefined|, and 4) stringifying all values 1.328 + * which pass the filter. 1.329 + */ 1.330 + id = propertyList[i]; 1.331 + RootedValue outputValue(cx); 1.332 + if (!JSObject::getGeneric(cx, obj, obj, id, &outputValue)) 1.333 + return false; 1.334 + if (!PreprocessValue(cx, obj, HandleId(id), &outputValue, scx)) 1.335 + return false; 1.336 + if (IsFilteredValue(outputValue)) 1.337 + continue; 1.338 + 1.339 + /* Output a comma unless this is the first member to write. */ 1.340 + if (wroteMember && !scx->sb.append(',')) 1.341 + return false; 1.342 + wroteMember = true; 1.343 + 1.344 + if (!WriteIndent(cx, scx, scx->depth)) 1.345 + return false; 1.346 + 1.347 + JSString *s = IdToString(cx, id); 1.348 + if (!s) 1.349 + return false; 1.350 + 1.351 + if (!Quote(cx, scx->sb, s) || 1.352 + !scx->sb.append(':') || 1.353 + !(scx->gap.empty() || scx->sb.append(' ')) || 1.354 + !Str(cx, outputValue, scx)) 1.355 + { 1.356 + return false; 1.357 + } 1.358 + } 1.359 + 1.360 + if (wroteMember && !WriteIndent(cx, scx, scx->depth - 1)) 1.361 + return false; 1.362 + 1.363 + return scx->sb.append('}'); 1.364 +} 1.365 + 1.366 +/* ES5 15.12.3 JA. */ 1.367 +static bool 1.368 +JA(JSContext *cx, HandleObject obj, StringifyContext *scx) 1.369 +{ 1.370 + /* 1.371 + * This method implements the JA algorithm in ES5 15.12.3, but: 1.372 + * 1.373 + * * The algorithm is somewhat reformulated to allow the final string to 1.374 + * be streamed into a single buffer, rather than be created and copied 1.375 + * into place incrementally as the ES5 algorithm specifies it. This 1.376 + * requires moving portions of the Str call in 8a into this algorithm 1.377 + * (and in JO as well). 1.378 + */ 1.379 + 1.380 + /* Steps 1-2, 11. */ 1.381 + AutoCycleDetector detect(cx, obj); 1.382 + if (!detect.init()) 1.383 + return false; 1.384 + if (detect.foundCycle()) { 1.385 + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CYCLIC_VALUE, js_object_str); 1.386 + return false; 1.387 + } 1.388 + 1.389 + if (!scx->sb.append('[')) 1.390 + return false; 1.391 + 1.392 + /* Step 6. */ 1.393 + uint32_t length; 1.394 + if (!GetLengthProperty(cx, obj, &length)) 1.395 + return false; 1.396 + 1.397 + /* Steps 7-10. */ 1.398 + if (length != 0) { 1.399 + /* Steps 4, 10b(i). */ 1.400 + if (!WriteIndent(cx, scx, scx->depth)) 1.401 + return false; 1.402 + 1.403 + /* Steps 7-10. */ 1.404 + RootedValue outputValue(cx); 1.405 + for (uint32_t i = 0; i < length; i++) { 1.406 + /* 1.407 + * Steps 8a-8c. Again note how the call to the spec's Str method 1.408 + * is broken up into getting the property, running it past toJSON 1.409 + * and the replacer and maybe unboxing, and interpreting some 1.410 + * values as |null| in separate steps. 1.411 + */ 1.412 + if (!JSObject::getElement(cx, obj, obj, i, &outputValue)) 1.413 + return false; 1.414 + if (!PreprocessValue(cx, obj, i, &outputValue, scx)) 1.415 + return false; 1.416 + if (IsFilteredValue(outputValue)) { 1.417 + if (!scx->sb.append("null")) 1.418 + return false; 1.419 + } else { 1.420 + if (!Str(cx, outputValue, scx)) 1.421 + return false; 1.422 + } 1.423 + 1.424 + /* Steps 3, 4, 10b(i). */ 1.425 + if (i < length - 1) { 1.426 + if (!scx->sb.append(',')) 1.427 + return false; 1.428 + if (!WriteIndent(cx, scx, scx->depth)) 1.429 + return false; 1.430 + } 1.431 + } 1.432 + 1.433 + /* Step 10(b)(iii). */ 1.434 + if (!WriteIndent(cx, scx, scx->depth - 1)) 1.435 + return false; 1.436 + } 1.437 + 1.438 + return scx->sb.append(']'); 1.439 +} 1.440 + 1.441 +static bool 1.442 +Str(JSContext *cx, const Value &v, StringifyContext *scx) 1.443 +{ 1.444 + /* Step 11 must be handled by the caller. */ 1.445 + JS_ASSERT(!IsFilteredValue(v)); 1.446 + 1.447 + JS_CHECK_RECURSION(cx, return false); 1.448 + 1.449 + /* 1.450 + * This method implements the Str algorithm in ES5 15.12.3, but: 1.451 + * 1.452 + * * We move property retrieval (step 1) into callers to stream the 1.453 + * stringification process and avoid constantly copying strings. 1.454 + * * We move the preprocessing in steps 2-4 into a helper function to 1.455 + * allow both JO and JA to use this method. While JA could use it 1.456 + * without this move, JO must omit any |undefined|-valued property per 1.457 + * so it can't stream out a value using the Str method exactly as 1.458 + * defined by ES5. 1.459 + * * We move step 11 into callers, again to ease streaming. 1.460 + */ 1.461 + 1.462 + /* Step 8. */ 1.463 + if (v.isString()) 1.464 + return Quote(cx, scx->sb, v.toString()); 1.465 + 1.466 + /* Step 5. */ 1.467 + if (v.isNull()) 1.468 + return scx->sb.append("null"); 1.469 + 1.470 + /* Steps 6-7. */ 1.471 + if (v.isBoolean()) 1.472 + return v.toBoolean() ? scx->sb.append("true") : scx->sb.append("false"); 1.473 + 1.474 + /* Step 9. */ 1.475 + if (v.isNumber()) { 1.476 + if (v.isDouble()) { 1.477 + if (!IsFinite(v.toDouble())) 1.478 + return scx->sb.append("null"); 1.479 + } 1.480 + 1.481 + StringBuffer sb(cx); 1.482 + if (!NumberValueToStringBuffer(cx, v, sb)) 1.483 + return false; 1.484 + 1.485 + return scx->sb.append(sb.begin(), sb.length()); 1.486 + } 1.487 + 1.488 + /* Step 10. */ 1.489 + JS_ASSERT(v.isObject()); 1.490 + RootedObject obj(cx, &v.toObject()); 1.491 + 1.492 + scx->depth++; 1.493 + bool ok; 1.494 + if (ObjectClassIs(obj, ESClass_Array, cx)) 1.495 + ok = JA(cx, obj, scx); 1.496 + else 1.497 + ok = JO(cx, obj, scx); 1.498 + scx->depth--; 1.499 + 1.500 + return ok; 1.501 +} 1.502 + 1.503 +/* ES5 15.12.3. */ 1.504 +bool 1.505 +js_Stringify(JSContext *cx, MutableHandleValue vp, JSObject *replacer_, Value space_, 1.506 + StringBuffer &sb) 1.507 +{ 1.508 + RootedObject replacer(cx, replacer_); 1.509 + RootedValue space(cx, space_); 1.510 + 1.511 + /* Step 4. */ 1.512 + AutoIdVector propertyList(cx); 1.513 + if (replacer) { 1.514 + if (replacer->isCallable()) { 1.515 + /* Step 4a(i): use replacer to transform values. */ 1.516 + } else if (ObjectClassIs(replacer, ESClass_Array, cx)) { 1.517 + /* 1.518 + * Step 4b: The spec algorithm is unhelpfully vague about the exact 1.519 + * steps taken when the replacer is an array, regarding the exact 1.520 + * sequence of [[Get]] calls for the array's elements, when its 1.521 + * overall length is calculated, whether own or own plus inherited 1.522 + * properties are considered, and so on. A rewrite was proposed in 1.523 + * <https://mail.mozilla.org/pipermail/es5-discuss/2011-April/003976.html>, 1.524 + * whose steps are copied below, and which are implemented here. 1.525 + * 1.526 + * i. Let PropertyList be an empty internal List. 1.527 + * ii. Let len be the result of calling the [[Get]] internal 1.528 + * method of replacer with the argument "length". 1.529 + * iii. Let i be 0. 1.530 + * iv. While i < len: 1.531 + * 1. Let item be undefined. 1.532 + * 2. Let v be the result of calling the [[Get]] internal 1.533 + * method of replacer with the argument ToString(i). 1.534 + * 3. If Type(v) is String then let item be v. 1.535 + * 4. Else if Type(v) is Number then let item be ToString(v). 1.536 + * 5. Else if Type(v) is Object then 1.537 + * a. If the [[Class]] internal property of v is "String" 1.538 + * or "Number" then let item be ToString(v). 1.539 + * 6. If item is not undefined and item is not currently an 1.540 + * element of PropertyList then, 1.541 + * a. Append item to the end of PropertyList. 1.542 + * 7. Let i be i + 1. 1.543 + */ 1.544 + 1.545 + /* Step 4b(ii). */ 1.546 + uint32_t len; 1.547 + JS_ALWAYS_TRUE(GetLengthProperty(cx, replacer, &len)); 1.548 + if (replacer->is<ArrayObject>() && !replacer->isIndexed()) 1.549 + len = Min(len, replacer->getDenseInitializedLength()); 1.550 + 1.551 + // Cap the initial size to a moderately small value. This avoids 1.552 + // ridiculous over-allocation if an array with bogusly-huge length 1.553 + // is passed in. If we end up having to add elements past this 1.554 + // size, the set will naturally resize to accommodate them. 1.555 + const uint32_t MaxInitialSize = 1024; 1.556 + HashSet<jsid, JsidHasher> idSet(cx); 1.557 + if (!idSet.init(Min(len, MaxInitialSize))) 1.558 + return false; 1.559 + 1.560 + /* Step 4b(iii). */ 1.561 + uint32_t i = 0; 1.562 + 1.563 + /* Step 4b(iv). */ 1.564 + RootedValue v(cx); 1.565 + for (; i < len; i++) { 1.566 + if (!CheckForInterrupt(cx)) 1.567 + return false; 1.568 + 1.569 + /* Step 4b(iv)(2). */ 1.570 + if (!JSObject::getElement(cx, replacer, replacer, i, &v)) 1.571 + return false; 1.572 + 1.573 + RootedId id(cx); 1.574 + if (v.isNumber()) { 1.575 + /* Step 4b(iv)(4). */ 1.576 + int32_t n; 1.577 + if (v.isNumber() && ValueFitsInInt32(v, &n) && INT_FITS_IN_JSID(n)) { 1.578 + id = INT_TO_JSID(n); 1.579 + } else { 1.580 + if (!ValueToId<CanGC>(cx, v, &id)) 1.581 + return false; 1.582 + } 1.583 + } else if (v.isString() || 1.584 + IsObjectWithClass(v, ESClass_String, cx) || 1.585 + IsObjectWithClass(v, ESClass_Number, cx)) 1.586 + { 1.587 + /* Step 4b(iv)(3), 4b(iv)(5). */ 1.588 + if (!ValueToId<CanGC>(cx, v, &id)) 1.589 + return false; 1.590 + } else { 1.591 + continue; 1.592 + } 1.593 + 1.594 + /* Step 4b(iv)(6). */ 1.595 + HashSet<jsid, JsidHasher>::AddPtr p = idSet.lookupForAdd(id); 1.596 + if (!p) { 1.597 + /* Step 4b(iv)(6)(a). */ 1.598 + if (!idSet.add(p, id) || !propertyList.append(id)) 1.599 + return false; 1.600 + } 1.601 + } 1.602 + } else { 1.603 + replacer = nullptr; 1.604 + } 1.605 + } 1.606 + 1.607 + /* Step 5. */ 1.608 + if (space.isObject()) { 1.609 + RootedObject spaceObj(cx, &space.toObject()); 1.610 + if (ObjectClassIs(spaceObj, ESClass_Number, cx)) { 1.611 + double d; 1.612 + if (!ToNumber(cx, space, &d)) 1.613 + return false; 1.614 + space = NumberValue(d); 1.615 + } else if (ObjectClassIs(spaceObj, ESClass_String, cx)) { 1.616 + JSString *str = ToStringSlow<CanGC>(cx, space); 1.617 + if (!str) 1.618 + return false; 1.619 + space = StringValue(str); 1.620 + } 1.621 + } 1.622 + 1.623 + StringBuffer gap(cx); 1.624 + 1.625 + if (space.isNumber()) { 1.626 + /* Step 6. */ 1.627 + double d; 1.628 + JS_ALWAYS_TRUE(ToInteger(cx, space, &d)); 1.629 + d = Min(10.0, d); 1.630 + if (d >= 1 && !gap.appendN(' ', uint32_t(d))) 1.631 + return false; 1.632 + } else if (space.isString()) { 1.633 + /* Step 7. */ 1.634 + JSLinearString *str = space.toString()->ensureLinear(cx); 1.635 + if (!str) 1.636 + return false; 1.637 + JS::Anchor<JSString *> anchor(str); 1.638 + size_t len = Min(size_t(10), space.toString()->length()); 1.639 + if (!gap.append(str->chars(), len)) 1.640 + return false; 1.641 + } else { 1.642 + /* Step 8. */ 1.643 + JS_ASSERT(gap.empty()); 1.644 + } 1.645 + 1.646 + /* Step 9. */ 1.647 + RootedObject wrapper(cx, NewBuiltinClassInstance(cx, &JSObject::class_)); 1.648 + if (!wrapper) 1.649 + return false; 1.650 + 1.651 + /* Step 10. */ 1.652 + RootedId emptyId(cx, NameToId(cx->names().empty)); 1.653 + if (!DefineNativeProperty(cx, wrapper, emptyId, vp, JS_PropertyStub, JS_StrictPropertyStub, 1.654 + JSPROP_ENUMERATE)) 1.655 + { 1.656 + return false; 1.657 + } 1.658 + 1.659 + /* Step 11. */ 1.660 + StringifyContext scx(cx, sb, gap, replacer, propertyList); 1.661 + if (!PreprocessValue(cx, wrapper, HandleId(emptyId), vp, &scx)) 1.662 + return false; 1.663 + if (IsFilteredValue(vp)) 1.664 + return true; 1.665 + 1.666 + return Str(cx, vp, &scx); 1.667 +} 1.668 + 1.669 +/* ES5 15.12.2 Walk. */ 1.670 +static bool 1.671 +Walk(JSContext *cx, HandleObject holder, HandleId name, HandleValue reviver, MutableHandleValue vp) 1.672 +{ 1.673 + JS_CHECK_RECURSION(cx, return false); 1.674 + 1.675 + /* Step 1. */ 1.676 + RootedValue val(cx); 1.677 + if (!JSObject::getGeneric(cx, holder, holder, name, &val)) 1.678 + return false; 1.679 + 1.680 + /* Step 2. */ 1.681 + if (val.isObject()) { 1.682 + RootedObject obj(cx, &val.toObject()); 1.683 + 1.684 + if (ObjectClassIs(obj, ESClass_Array, cx)) { 1.685 + /* Step 2a(ii). */ 1.686 + uint32_t length; 1.687 + if (!GetLengthProperty(cx, obj, &length)) 1.688 + return false; 1.689 + 1.690 + /* Step 2a(i), 2a(iii-iv). */ 1.691 + RootedId id(cx); 1.692 + RootedValue newElement(cx); 1.693 + for (uint32_t i = 0; i < length; i++) { 1.694 + if (!IndexToId(cx, i, &id)) 1.695 + return false; 1.696 + 1.697 + /* Step 2a(iii)(1). */ 1.698 + if (!Walk(cx, obj, id, reviver, &newElement)) 1.699 + return false; 1.700 + 1.701 + if (newElement.isUndefined()) { 1.702 + /* Step 2a(iii)(2). */ 1.703 + bool succeeded; 1.704 + if (!JSObject::deleteByValue(cx, obj, IdToValue(id), &succeeded)) 1.705 + return false; 1.706 + } else { 1.707 + /* Step 2a(iii)(3). */ 1.708 + // XXX This definition should ignore success/failure, when 1.709 + // our property-definition APIs indicate that. 1.710 + if (!JSObject::defineGeneric(cx, obj, id, newElement)) 1.711 + return false; 1.712 + } 1.713 + } 1.714 + } else { 1.715 + /* Step 2b(i). */ 1.716 + AutoIdVector keys(cx); 1.717 + if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, &keys)) 1.718 + return false; 1.719 + 1.720 + /* Step 2b(ii). */ 1.721 + RootedId id(cx); 1.722 + RootedValue newElement(cx); 1.723 + for (size_t i = 0, len = keys.length(); i < len; i++) { 1.724 + /* Step 2b(ii)(1). */ 1.725 + id = keys[i]; 1.726 + if (!Walk(cx, obj, id, reviver, &newElement)) 1.727 + return false; 1.728 + 1.729 + if (newElement.isUndefined()) { 1.730 + /* Step 2b(ii)(2). */ 1.731 + bool succeeded; 1.732 + if (!JSObject::deleteByValue(cx, obj, IdToValue(id), &succeeded)) 1.733 + return false; 1.734 + } else { 1.735 + /* Step 2b(ii)(3). */ 1.736 + // XXX This definition should ignore success/failure, when 1.737 + // our property-definition APIs indicate that. 1.738 + if (!JSObject::defineGeneric(cx, obj, id, newElement)) 1.739 + return false; 1.740 + } 1.741 + } 1.742 + } 1.743 + } 1.744 + 1.745 + /* Step 3. */ 1.746 + RootedString key(cx, IdToString(cx, name)); 1.747 + if (!key) 1.748 + return false; 1.749 + 1.750 + InvokeArgs args(cx); 1.751 + if (!args.init(2)) 1.752 + return false; 1.753 + 1.754 + args.setCallee(reviver); 1.755 + args.setThis(ObjectValue(*holder)); 1.756 + args[0].setString(key); 1.757 + args[1].set(val); 1.758 + 1.759 + if (!Invoke(cx, args)) 1.760 + return false; 1.761 + vp.set(args.rval()); 1.762 + return true; 1.763 +} 1.764 + 1.765 +static bool 1.766 +Revive(JSContext *cx, HandleValue reviver, MutableHandleValue vp) 1.767 +{ 1.768 + RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_)); 1.769 + if (!obj) 1.770 + return false; 1.771 + 1.772 + if (!JSObject::defineProperty(cx, obj, cx->names().empty, vp)) 1.773 + return false; 1.774 + 1.775 + Rooted<jsid> id(cx, NameToId(cx->names().empty)); 1.776 + return Walk(cx, obj, id, reviver, vp); 1.777 +} 1.778 + 1.779 +bool 1.780 +js::ParseJSONWithReviver(JSContext *cx, ConstTwoByteChars chars, size_t length, 1.781 + HandleValue reviver, MutableHandleValue vp) 1.782 +{ 1.783 + /* 15.12.2 steps 2-3. */ 1.784 + JSONParser parser(cx, chars, length); 1.785 + if (!parser.parse(vp)) 1.786 + return false; 1.787 + 1.788 + /* 15.12.2 steps 4-5. */ 1.789 + if (js_IsCallable(reviver)) 1.790 + return Revive(cx, reviver, vp); 1.791 + return true; 1.792 +} 1.793 + 1.794 +#if JS_HAS_TOSOURCE 1.795 +static bool 1.796 +json_toSource(JSContext *cx, unsigned argc, Value *vp) 1.797 +{ 1.798 + CallArgs args = CallArgsFromVp(argc, vp); 1.799 + args.rval().setString(cx->names().JSON); 1.800 + return true; 1.801 +} 1.802 +#endif 1.803 + 1.804 +/* ES5 15.12.2. */ 1.805 +static bool 1.806 +json_parse(JSContext *cx, unsigned argc, Value *vp) 1.807 +{ 1.808 + CallArgs args = CallArgsFromVp(argc, vp); 1.809 + 1.810 + /* Step 1. */ 1.811 + JSString *str = (args.length() >= 1) 1.812 + ? ToString<CanGC>(cx, args[0]) 1.813 + : cx->names().undefined; 1.814 + if (!str) 1.815 + return false; 1.816 + 1.817 + Rooted<JSFlatString*> flat(cx, str->ensureFlat(cx)); 1.818 + if (!flat) 1.819 + return false; 1.820 + 1.821 + JS::Anchor<JSString *> anchor(flat); 1.822 + 1.823 + RootedValue reviver(cx, args.get(1)); 1.824 + 1.825 + /* Steps 2-5. */ 1.826 + return ParseJSONWithReviver(cx, ConstTwoByteChars(flat->chars(), flat->length()), 1.827 + flat->length(), reviver, args.rval()); 1.828 +} 1.829 + 1.830 +/* ES5 15.12.3. */ 1.831 +bool 1.832 +json_stringify(JSContext *cx, unsigned argc, Value *vp) 1.833 +{ 1.834 + CallArgs args = CallArgsFromVp(argc, vp); 1.835 + RootedObject replacer(cx, args.get(1).isObject() ? &args[1].toObject() : nullptr); 1.836 + RootedValue value(cx, args.get(0)); 1.837 + RootedValue space(cx, args.get(2)); 1.838 + 1.839 + StringBuffer sb(cx); 1.840 + if (!js_Stringify(cx, &value, replacer, space, sb)) 1.841 + return false; 1.842 + 1.843 + // XXX This can never happen to nsJSON.cpp, but the JSON object 1.844 + // needs to support returning undefined. So this is a little awkward 1.845 + // for the API, because we want to support streaming writers. 1.846 + if (!sb.empty()) { 1.847 + JSString *str = sb.finishString(); 1.848 + if (!str) 1.849 + return false; 1.850 + args.rval().setString(str); 1.851 + } else { 1.852 + args.rval().setUndefined(); 1.853 + } 1.854 + 1.855 + return true; 1.856 +} 1.857 + 1.858 +static const JSFunctionSpec json_static_methods[] = { 1.859 +#if JS_HAS_TOSOURCE 1.860 + JS_FN(js_toSource_str, json_toSource, 0, 0), 1.861 +#endif 1.862 + JS_FN("parse", json_parse, 2, 0), 1.863 + JS_FN("stringify", json_stringify, 3, 0), 1.864 + JS_FS_END 1.865 +}; 1.866 + 1.867 +JSObject * 1.868 +js_InitJSONClass(JSContext *cx, HandleObject obj) 1.869 +{ 1.870 + Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>()); 1.871 + 1.872 + /* 1.873 + * JSON requires that Boolean.prototype.valueOf be created and stashed in a 1.874 + * reserved slot on the global object; see js::BooleanGetPrimitiveValueSlow 1.875 + * called from PreprocessValue above. 1.876 + */ 1.877 + if (!GlobalObject::getOrCreateBooleanPrototype(cx, global)) 1.878 + return nullptr; 1.879 + 1.880 + RootedObject proto(cx, obj->as<GlobalObject>().getOrCreateObjectPrototype(cx)); 1.881 + RootedObject JSON(cx, NewObjectWithClassProto(cx, &JSONClass, proto, global, SingletonObject)); 1.882 + if (!JSON) 1.883 + return nullptr; 1.884 + 1.885 + if (!JS_DefineProperty(cx, global, js_JSON_str, JSON, 0, 1.886 + JS_PropertyStub, JS_StrictPropertyStub)) 1.887 + return nullptr; 1.888 + 1.889 + if (!JS_DefineFunctions(cx, JSON, json_static_methods)) 1.890 + return nullptr; 1.891 + 1.892 + global->setConstructor(JSProto_JSON, ObjectValue(*JSON)); 1.893 + 1.894 + return JSON; 1.895 +}