js/src/json.cpp

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

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

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

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

mercurial