/js/src/json.cpp

http://github.com/zpao/v8monkey · C++ · 945 lines · 627 code · 119 blank · 199 comment · 161 complexity · 5fb6abcd114cf7a38a1a3aac9bb50fdb MD5 · raw file

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