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 : }
|