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/. */
9 #include "mozilla/FloatingPoint.h"
10 #include "mozilla/Range.h"
17 #include "jsonparser.h"
22 #include "vm/Interpreter.h"
23 #include "vm/StringBuffer.h"
25 #include "jsatominlines.h"
26 #include "jsboolinlines.h"
27 #include "jsobjinlines.h"
30 using namespace js::gc
;
31 using namespace js::types
;
33 using mozilla::IsFinite
;
35 using mozilla::RangedPtr
;
37 const Class
js::JSONClass
= {
39 JSCLASS_HAS_CACHED_PROTO(JSProto_JSON
),
40 JS_PropertyStub
, /* addProperty */
41 JS_DeletePropertyStub
, /* delProperty */
42 JS_PropertyStub
, /* getProperty */
43 JS_StrictPropertyStub
, /* setProperty */
49 static inline bool IsQuoteSpecialCharacter(jschar c
)
51 JS_STATIC_ASSERT('\b' < ' ');
52 JS_STATIC_ASSERT('\f' < ' ');
53 JS_STATIC_ASSERT('\n' < ' ');
54 JS_STATIC_ASSERT('\r' < ' ');
55 JS_STATIC_ASSERT('\t' < ' ');
56 return c
== '"' || c
== '\\' || c
< ' ';
59 /* ES5 15.12.3 Quote. */
60 template <typename CharT
>
62 Quote(StringBuffer
&sb
, JSLinearString
*str
)
64 size_t len
= str
->length();
71 JS::AutoCheckCannotGC nogc
;
72 const RangedPtr
<const CharT
> buf(str
->chars
<CharT
>(nogc
), len
);
73 for (size_t i
= 0; i
< len
; ++i
) {
74 /* Batch-append maximal character sequences containing no escapes. */
77 if (IsQuoteSpecialCharacter(buf
[i
]))
81 if (!sb
.appendSubstring(str
, mark
, i
- mark
))
88 if (c
== '"' || c
== '\\') {
89 if (!sb
.append('\\') || !sb
.append(c
))
91 } else if (c
== '\b' || c
== '\f' || c
== '\n' || c
== '\r' || c
== '\t') {
92 jschar abbrev
= (c
== '\b')
101 if (!sb
.append('\\') || !sb
.append(abbrev
))
105 if (!sb
.append("\\u00"))
107 JS_ASSERT((c
>> 4) < 10);
108 uint8_t x
= c
>> 4, y
= c
% 16;
109 if (!sb
.append(Latin1Char('0' + x
)) ||
110 !sb
.append(Latin1Char(y
< 10 ? '0' + y
: 'a' + (y
- 10))))
118 return sb
.append('"');
122 Quote(JSContext
*cx
, StringBuffer
&sb
, JSString
*str
)
124 JS::Anchor
<JSString
*> anchor(str
);
125 JSLinearString
*linear
= str
->ensureLinear(cx
);
129 return linear
->hasLatin1Chars()
130 ? Quote
<Latin1Char
>(sb
, linear
)
131 : Quote
<jschar
>(sb
, linear
);
136 class StringifyContext
139 StringifyContext(JSContext
*cx
, StringBuffer
&sb
, const StringBuffer
&gap
,
140 HandleObject replacer
, const AutoIdVector
&propertyList
)
143 replacer(cx
, replacer
),
144 propertyList(propertyList
),
149 const StringBuffer
&gap
;
150 RootedObject replacer
;
151 const AutoIdVector
&propertyList
;
155 } /* anonymous namespace */
157 static bool Str(JSContext
*cx
, const Value
&v
, StringifyContext
*scx
);
160 WriteIndent(JSContext
*cx
, StringifyContext
*scx
, uint32_t limit
)
162 if (!scx
->gap
.empty()) {
163 if (!scx
->sb
.append('\n'))
166 if (scx
->gap
.isUnderlyingBufferLatin1()) {
167 for (uint32_t i
= 0; i
< limit
; i
++) {
168 if (!scx
->sb
.append(scx
->gap
.rawLatin1Begin(), scx
->gap
.rawLatin1End()))
172 for (uint32_t i
= 0; i
< limit
; i
++) {
173 if (!scx
->sb
.append(scx
->gap
.rawTwoByteBegin(), scx
->gap
.rawTwoByteEnd()))
184 template<typename KeyType
>
185 class KeyStringifier
{
189 class KeyStringifier
<uint32_t> {
191 static JSString
*toString(JSContext
*cx
, uint32_t index
) {
192 return IndexToString(cx
, index
);
197 class KeyStringifier
<HandleId
> {
199 static JSString
*toString(JSContext
*cx
, HandleId id
) {
200 return IdToString(cx
, id
);
204 } /* anonymous namespace */
207 * ES5 15.12.3 Str, steps 2-4, extracted to enable preprocessing of property
208 * values when stringifying objects in JO.
210 template<typename KeyType
>
212 PreprocessValue(JSContext
*cx
, HandleObject holder
, KeyType key
, MutableHandleValue vp
, StringifyContext
*scx
)
214 RootedString
keyStr(cx
);
218 RootedValue
toJSON(cx
);
219 RootedObject
obj(cx
, &vp
.toObject());
220 if (!JSObject::getProperty(cx
, obj
, obj
, cx
->names().toJSON
, &toJSON
))
223 if (IsCallable(toJSON
)) {
224 keyStr
= KeyStringifier
<KeyType
>::toString(cx
, key
);
232 args
.setCallee(toJSON
);
234 args
[0].setString(keyStr
);
236 if (!Invoke(cx
, args
))
243 if (scx
->replacer
&& scx
->replacer
->isCallable()) {
245 keyStr
= KeyStringifier
<KeyType
>::toString(cx
, key
);
254 args
.setCallee(ObjectValue(*scx
->replacer
));
255 args
.setThis(ObjectValue(*holder
));
256 args
[0].setString(keyStr
);
259 if (!Invoke(cx
, args
))
265 if (vp
.get().isObject()) {
266 RootedObject
obj(cx
, &vp
.get().toObject());
267 if (ObjectClassIs(obj
, ESClass_Number
, cx
)) {
269 if (!ToNumber(cx
, vp
, &d
))
272 } else if (ObjectClassIs(obj
, ESClass_String
, cx
)) {
273 JSString
*str
= ToStringSlow
<CanGC
>(cx
, vp
);
277 } else if (ObjectClassIs(obj
, ESClass_Boolean
, cx
)) {
278 if (!Unbox(cx
, obj
, vp
))
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
294 IsFilteredValue(const Value
&v
)
296 return v
.isUndefined() || v
.isSymbol() || IsCallable(v
);
299 /* ES5 15.12.3 JO. */
301 JO(JSContext
*cx
, HandleObject obj
, StringifyContext
*scx
)
304 * This method implements the JO algorithm in ES5 15.12.3, but:
306 * * The algorithm is somewhat reformulated to allow the final string to
307 * be streamed into a single buffer, rather than be created and copied
308 * into place incrementally as the ES5 algorithm specifies it. This
309 * requires moving portions of the Str call in 8a into this algorithm
310 * (and in JA as well).
314 AutoCycleDetector
detect(cx
, obj
);
317 if (detect
.foundCycle()) {
318 JS_ReportErrorNumber(cx
, js_GetErrorMessage
, nullptr, JSMSG_JSON_CYCLIC_VALUE
,
323 if (!scx
->sb
.append('{'))
327 Maybe
<AutoIdVector
> ids
;
328 const AutoIdVector
*props
;
329 if (scx
->replacer
&& !scx
->replacer
->isCallable()) {
330 JS_ASSERT(IsArray(scx
->replacer
, cx
));
331 props
= &scx
->propertyList
;
333 JS_ASSERT_IF(scx
->replacer
, scx
->propertyList
.length() == 0);
335 if (!GetPropertyNames(cx
, obj
, JSITER_OWNONLY
, ids
.ptr()))
340 /* My kingdom for not-quite-initialized-from-the-start references. */
341 const AutoIdVector
&propertyList
= *props
;
343 /* Steps 8-10, 13. */
344 bool wroteMember
= false;
346 for (size_t i
= 0, len
= propertyList
.length(); i
< len
; i
++) {
348 * Steps 8a-8b. Note that the call to Str is broken up into 1) getting
349 * the property; 2) processing for toJSON, calling the replacer, and
350 * handling boxed Number/String/Boolean objects; 3) filtering out
351 * values which process to |undefined|, and 4) stringifying all values
352 * which pass the filter.
354 id
= propertyList
[i
];
355 RootedValue
outputValue(cx
);
356 if (!JSObject::getGeneric(cx
, obj
, obj
, id
, &outputValue
))
358 if (!PreprocessValue(cx
, obj
, HandleId(id
), &outputValue
, scx
))
360 if (IsFilteredValue(outputValue
))
363 /* Output a comma unless this is the first member to write. */
364 if (wroteMember
&& !scx
->sb
.append(','))
368 if (!WriteIndent(cx
, scx
, scx
->depth
))
371 JSString
*s
= IdToString(cx
, id
);
375 if (!Quote(cx
, scx
->sb
, s
) ||
376 !scx
->sb
.append(':') ||
377 !(scx
->gap
.empty() || scx
->sb
.append(' ')) ||
378 !Str(cx
, outputValue
, scx
))
384 if (wroteMember
&& !WriteIndent(cx
, scx
, scx
->depth
- 1))
387 return scx
->sb
.append('}');
390 /* ES5 15.12.3 JA. */
392 JA(JSContext
*cx
, HandleObject obj
, StringifyContext
*scx
)
395 * This method implements the JA algorithm in ES5 15.12.3, but:
397 * * The algorithm is somewhat reformulated to allow the final string to
398 * be streamed into a single buffer, rather than be created and copied
399 * into place incrementally as the ES5 algorithm specifies it. This
400 * requires moving portions of the Str call in 8a into this algorithm
401 * (and in JO as well).
405 AutoCycleDetector
detect(cx
, obj
);
408 if (detect
.foundCycle()) {
409 JS_ReportErrorNumber(cx
, js_GetErrorMessage
, nullptr, JSMSG_JSON_CYCLIC_VALUE
,
414 if (!scx
->sb
.append('['))
419 if (!GetLengthProperty(cx
, obj
, &length
))
424 /* Steps 4, 10b(i). */
425 if (!WriteIndent(cx
, scx
, scx
->depth
))
429 RootedValue
outputValue(cx
);
430 for (uint32_t i
= 0; i
< length
; i
++) {
432 * Steps 8a-8c. Again note how the call to the spec's Str method
433 * is broken up into getting the property, running it past toJSON
434 * and the replacer and maybe unboxing, and interpreting some
435 * values as |null| in separate steps.
437 if (!JSObject::getElement(cx
, obj
, obj
, i
, &outputValue
))
439 if (!PreprocessValue(cx
, obj
, i
, &outputValue
, scx
))
441 if (IsFilteredValue(outputValue
)) {
442 if (!scx
->sb
.append("null"))
445 if (!Str(cx
, outputValue
, scx
))
449 /* Steps 3, 4, 10b(i). */
450 if (i
< length
- 1) {
451 if (!scx
->sb
.append(','))
453 if (!WriteIndent(cx
, scx
, scx
->depth
))
458 /* Step 10(b)(iii). */
459 if (!WriteIndent(cx
, scx
, scx
->depth
- 1))
463 return scx
->sb
.append(']');
467 Str(JSContext
*cx
, const Value
&v
, StringifyContext
*scx
)
469 /* Step 11 must be handled by the caller. */
470 JS_ASSERT(!IsFilteredValue(v
));
472 JS_CHECK_RECURSION(cx
, return false);
475 * This method implements the Str algorithm in ES5 15.12.3, but:
477 * * We move property retrieval (step 1) into callers to stream the
478 * stringification process and avoid constantly copying strings.
479 * * We move the preprocessing in steps 2-4 into a helper function to
480 * allow both JO and JA to use this method. While JA could use it
481 * without this move, JO must omit any |undefined|-valued property per
482 * so it can't stream out a value using the Str method exactly as
484 * * We move step 11 into callers, again to ease streaming.
489 return Quote(cx
, scx
->sb
, v
.toString());
493 return scx
->sb
.append("null");
497 return v
.toBoolean() ? scx
->sb
.append("true") : scx
->sb
.append("false");
502 if (!IsFinite(v
.toDouble()))
503 return scx
->sb
.append("null");
506 return NumberValueToStringBuffer(cx
, v
, scx
->sb
);
510 JS_ASSERT(v
.isObject());
511 RootedObject
obj(cx
, &v
.toObject());
515 if (IsArray(obj
, cx
))
516 ok
= JA(cx
, obj
, scx
);
518 ok
= JO(cx
, obj
, scx
);
526 js_Stringify(JSContext
*cx
, MutableHandleValue vp
, JSObject
*replacer_
, Value space_
,
529 RootedObject
replacer(cx
, replacer_
);
530 RootedValue
space(cx
, space_
);
533 AutoIdVector
propertyList(cx
);
535 if (replacer
->isCallable()) {
536 /* Step 4a(i): use replacer to transform values. */
537 } else if (IsArray(replacer
, cx
)) {
539 * Step 4b: The spec algorithm is unhelpfully vague about the exact
540 * steps taken when the replacer is an array, regarding the exact
541 * sequence of [[Get]] calls for the array's elements, when its
542 * overall length is calculated, whether own or own plus inherited
543 * properties are considered, and so on. A rewrite was proposed in
544 * <https://mail.mozilla.org/pipermail/es5-discuss/2011-April/003976.html>,
545 * whose steps are copied below, and which are implemented here.
547 * i. Let PropertyList be an empty internal List.
548 * ii. Let len be the result of calling the [[Get]] internal
549 * method of replacer with the argument "length".
552 * 1. Let item be undefined.
553 * 2. Let v be the result of calling the [[Get]] internal
554 * method of replacer with the argument ToString(i).
555 * 3. If Type(v) is String then let item be v.
556 * 4. Else if Type(v) is Number then let item be ToString(v).
557 * 5. Else if Type(v) is Object then
558 * a. If the [[Class]] internal property of v is "String"
559 * or "Number" then let item be ToString(v).
560 * 6. If item is not undefined and item is not currently an
561 * element of PropertyList then,
562 * a. Append item to the end of PropertyList.
568 if (!GetLengthProperty(cx
, replacer
, &len
))
570 if (replacer
->is
<ArrayObject
>() && !replacer
->isIndexed())
571 len
= Min(len
, replacer
->getDenseInitializedLength());
573 // Cap the initial size to a moderately small value. This avoids
574 // ridiculous over-allocation if an array with bogusly-huge length
575 // is passed in. If we end up having to add elements past this
576 // size, the set will naturally resize to accommodate them.
577 const uint32_t MaxInitialSize
= 1024;
578 HashSet
<jsid
, JsidHasher
> idSet(cx
);
579 if (!idSet
.init(Min(len
, MaxInitialSize
)))
587 for (; i
< len
; i
++) {
588 if (!CheckForInterrupt(cx
))
591 /* Step 4b(iv)(2). */
592 if (!JSObject::getElement(cx
, replacer
, replacer
, i
, &v
))
597 /* Step 4b(iv)(4). */
599 if (v
.isNumber() && ValueFitsInInt32(v
, &n
) && INT_FITS_IN_JSID(n
)) {
602 if (!ValueToId
<CanGC
>(cx
, v
, &id
))
605 } else if (v
.isString() ||
606 IsObjectWithClass(v
, ESClass_String
, cx
) ||
607 IsObjectWithClass(v
, ESClass_Number
, cx
))
609 /* Step 4b(iv)(3), 4b(iv)(5). */
610 if (!ValueToId
<CanGC
>(cx
, v
, &id
))
616 /* Step 4b(iv)(6). */
617 HashSet
<jsid
, JsidHasher
>::AddPtr p
= idSet
.lookupForAdd(id
);
619 /* Step 4b(iv)(6)(a). */
620 if (!idSet
.add(p
, id
) || !propertyList
.append(id
))
630 if (space
.isObject()) {
631 RootedObject
spaceObj(cx
, &space
.toObject());
632 if (ObjectClassIs(spaceObj
, ESClass_Number
, cx
)) {
634 if (!ToNumber(cx
, space
, &d
))
636 space
= NumberValue(d
);
637 } else if (ObjectClassIs(spaceObj
, ESClass_String
, cx
)) {
638 JSString
*str
= ToStringSlow
<CanGC
>(cx
, space
);
641 space
= StringValue(str
);
645 StringBuffer
gap(cx
);
647 if (space
.isNumber()) {
650 JS_ALWAYS_TRUE(ToInteger(cx
, space
, &d
));
652 if (d
>= 1 && !gap
.appendN(' ', uint32_t(d
)))
654 } else if (space
.isString()) {
656 JSLinearString
*str
= space
.toString()->ensureLinear(cx
);
659 JS::Anchor
<JSString
*> anchor(str
);
660 size_t len
= Min(size_t(10), str
->length());
661 if (!gap
.appendSubstring(str
, 0, len
))
665 JS_ASSERT(gap
.empty());
669 RootedObject
wrapper(cx
, NewBuiltinClassInstance(cx
, &JSObject::class_
));
674 RootedId
emptyId(cx
, NameToId(cx
->names().empty
));
675 if (!DefineNativeProperty(cx
, wrapper
, emptyId
, vp
, JS_PropertyStub
, JS_StrictPropertyStub
,
682 StringifyContext
scx(cx
, sb
, gap
, replacer
, propertyList
);
683 if (!PreprocessValue(cx
, wrapper
, HandleId(emptyId
), vp
, &scx
))
685 if (IsFilteredValue(vp
))
688 return Str(cx
, vp
, &scx
);
691 /* ES5 15.12.2 Walk. */
693 Walk(JSContext
*cx
, HandleObject holder
, HandleId name
, HandleValue reviver
, MutableHandleValue vp
)
695 JS_CHECK_RECURSION(cx
, return false);
699 if (!JSObject::getGeneric(cx
, holder
, holder
, name
, &val
))
703 if (val
.isObject()) {
704 RootedObject
obj(cx
, &val
.toObject());
706 if (IsArray(obj
, cx
)) {
709 if (!GetLengthProperty(cx
, obj
, &length
))
712 /* Step 2a(i), 2a(iii-iv). */
714 RootedValue
newElement(cx
);
715 for (uint32_t i
= 0; i
< length
; i
++) {
716 if (!IndexToId(cx
, i
, &id
))
719 /* Step 2a(iii)(1). */
720 if (!Walk(cx
, obj
, id
, reviver
, &newElement
))
723 if (newElement
.isUndefined()) {
724 /* Step 2a(iii)(2). */
726 if (!JSObject::deleteGeneric(cx
, obj
, id
, &succeeded
))
729 /* Step 2a(iii)(3). */
730 // XXX This definition should ignore success/failure, when
731 // our property-definition APIs indicate that.
732 if (!JSObject::defineGeneric(cx
, obj
, id
, newElement
))
738 AutoIdVector
keys(cx
);
739 if (!GetPropertyNames(cx
, obj
, JSITER_OWNONLY
, &keys
))
744 RootedValue
newElement(cx
);
745 for (size_t i
= 0, len
= keys
.length(); i
< len
; i
++) {
746 /* Step 2b(ii)(1). */
748 if (!Walk(cx
, obj
, id
, reviver
, &newElement
))
751 if (newElement
.isUndefined()) {
752 /* Step 2b(ii)(2). */
754 if (!JSObject::deleteGeneric(cx
, obj
, id
, &succeeded
))
757 /* Step 2b(ii)(3). */
758 // XXX This definition should ignore success/failure, when
759 // our property-definition APIs indicate that.
760 if (!JSObject::defineGeneric(cx
, obj
, id
, newElement
))
768 RootedString
key(cx
, IdToString(cx
, name
));
776 args
.setCallee(reviver
);
777 args
.setThis(ObjectValue(*holder
));
778 args
[0].setString(key
);
781 if (!Invoke(cx
, args
))
788 Revive(JSContext
*cx
, HandleValue reviver
, MutableHandleValue vp
)
790 RootedObject
obj(cx
, NewBuiltinClassInstance(cx
, &JSObject::class_
));
794 if (!JSObject::defineProperty(cx
, obj
, cx
->names().empty
, vp
))
797 Rooted
<jsid
> id(cx
, NameToId(cx
->names().empty
));
798 return Walk(cx
, obj
, id
, reviver
, vp
);
801 template <typename CharT
>
803 js::ParseJSONWithReviver(JSContext
*cx
, const mozilla::Range
<const CharT
> chars
, HandleValue reviver
,
804 MutableHandleValue vp
)
806 /* 15.12.2 steps 2-3. */
807 JSONParser
<CharT
> parser(cx
, chars
);
808 if (!parser
.parse(vp
))
811 /* 15.12.2 steps 4-5. */
812 if (IsCallable(reviver
))
813 return Revive(cx
, reviver
, vp
);
818 js::ParseJSONWithReviver(JSContext
*cx
, const mozilla::Range
<const Latin1Char
> chars
,
819 HandleValue reviver
, MutableHandleValue vp
);
822 js::ParseJSONWithReviver(JSContext
*cx
, const mozilla::Range
<const jschar
> chars
, HandleValue reviver
,
823 MutableHandleValue vp
);
827 json_toSource(JSContext
*cx
, unsigned argc
, Value
*vp
)
829 CallArgs args
= CallArgsFromVp(argc
, vp
);
830 args
.rval().setString(cx
->names().JSON
);
837 json_parse(JSContext
*cx
, unsigned argc
, Value
*vp
)
839 CallArgs args
= CallArgsFromVp(argc
, vp
);
842 JSString
*str
= (args
.length() >= 1)
843 ? ToString
<CanGC
>(cx
, args
[0])
844 : cx
->names().undefined
;
848 JSFlatString
*flat
= str
->ensureFlat(cx
);
852 JS::Anchor
<JSString
*> anchor(flat
);
854 AutoStableStringChars
flatChars(cx
);
855 if (!flatChars
.init(cx
, flat
))
858 RootedValue
reviver(cx
, args
.get(1));
861 return flatChars
.isLatin1()
862 ? ParseJSONWithReviver(cx
, flatChars
.latin1Range(), reviver
, args
.rval())
863 : ParseJSONWithReviver(cx
, flatChars
.twoByteRange(), reviver
, args
.rval());
868 json_stringify(JSContext
*cx
, unsigned argc
, Value
*vp
)
870 CallArgs args
= CallArgsFromVp(argc
, vp
);
871 RootedObject
replacer(cx
, args
.get(1).isObject() ? &args
[1].toObject() : nullptr);
872 RootedValue
value(cx
, args
.get(0));
873 RootedValue
space(cx
, args
.get(2));
876 if (!js_Stringify(cx
, &value
, replacer
, space
, sb
))
879 // XXX This can never happen to nsJSON.cpp, but the JSON object
880 // needs to support returning undefined. So this is a little awkward
881 // for the API, because we want to support streaming writers.
883 JSString
*str
= sb
.finishString();
886 args
.rval().setString(str
);
888 args
.rval().setUndefined();
894 static const JSFunctionSpec json_static_methods
[] = {
896 JS_FN(js_toSource_str
, json_toSource
, 0, 0),
898 JS_FN("parse", json_parse
, 2, 0),
899 JS_FN("stringify", json_stringify
, 3, 0),
904 js_InitJSONClass(JSContext
*cx
, HandleObject obj
)
906 Rooted
<GlobalObject
*> global(cx
, &obj
->as
<GlobalObject
>());
908 RootedObject
proto(cx
, global
->getOrCreateObjectPrototype(cx
));
911 RootedObject
JSON(cx
, NewObjectWithGivenProto(cx
, &JSONClass
, proto
, global
, SingletonObject
));
915 if (!JS_DefineProperty(cx
, global
, js_JSON_str
, JSON
, 0,
916 JS_PropertyStub
, JS_StrictPropertyStub
))
919 if (!JS_DefineFunctions(cx
, JSON
, json_static_methods
))
922 global
->setConstructor(JSProto_JSON
, ObjectValue(*JSON
));