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
))
271 vp
.set(NumberValue(d
));
272 } else if (ObjectClassIs(obj
, ESClass_String
, cx
)) {
273 JSString
*str
= ToStringSlow
<CanGC
>(cx
, vp
);
276 vp
.set(StringValue(str
));
277 } else if (ObjectClassIs(obj
, ESClass_Boolean
, cx
)) {
278 vp
.setBoolean(BooleanGetPrimitiveValue(obj
));
286 * Determines whether a value which has passed by ES5 150.2.3 Str steps 1-4's
287 * gauntlet will result in Str returning |undefined|. This function is used to
288 * properly omit properties resulting in such values when stringifying objects,
289 * while properly stringifying such properties as null when they're encountered
293 IsFilteredValue(const Value
&v
)
295 return v
.isUndefined() || v
.isSymbol() || IsCallable(v
);
298 /* ES5 15.12.3 JO. */
300 JO(JSContext
*cx
, HandleObject obj
, StringifyContext
*scx
)
303 * This method implements the JO algorithm in ES5 15.12.3, but:
305 * * The algorithm is somewhat reformulated to allow the final string to
306 * be streamed into a single buffer, rather than be created and copied
307 * into place incrementally as the ES5 algorithm specifies it. This
308 * requires moving portions of the Str call in 8a into this algorithm
309 * (and in JA as well).
313 AutoCycleDetector
detect(cx
, obj
);
316 if (detect
.foundCycle()) {
317 JS_ReportErrorNumber(cx
, js_GetErrorMessage
, nullptr, JSMSG_JSON_CYCLIC_VALUE
,
322 if (!scx
->sb
.append('{'))
326 Maybe
<AutoIdVector
> ids
;
327 const AutoIdVector
*props
;
328 if (scx
->replacer
&& !scx
->replacer
->isCallable()) {
329 JS_ASSERT(JS_IsArrayObject(cx
, scx
->replacer
));
330 props
= &scx
->propertyList
;
332 JS_ASSERT_IF(scx
->replacer
, scx
->propertyList
.length() == 0);
334 if (!GetPropertyNames(cx
, obj
, JSITER_OWNONLY
, ids
.ptr()))
339 /* My kingdom for not-quite-initialized-from-the-start references. */
340 const AutoIdVector
&propertyList
= *props
;
342 /* Steps 8-10, 13. */
343 bool wroteMember
= false;
345 for (size_t i
= 0, len
= propertyList
.length(); i
< len
; i
++) {
347 * Steps 8a-8b. Note that the call to Str is broken up into 1) getting
348 * the property; 2) processing for toJSON, calling the replacer, and
349 * handling boxed Number/String/Boolean objects; 3) filtering out
350 * values which process to |undefined|, and 4) stringifying all values
351 * which pass the filter.
353 id
= propertyList
[i
];
354 RootedValue
outputValue(cx
);
355 if (!JSObject::getGeneric(cx
, obj
, obj
, id
, &outputValue
))
357 if (!PreprocessValue(cx
, obj
, HandleId(id
), &outputValue
, scx
))
359 if (IsFilteredValue(outputValue
))
362 /* Output a comma unless this is the first member to write. */
363 if (wroteMember
&& !scx
->sb
.append(','))
367 if (!WriteIndent(cx
, scx
, scx
->depth
))
370 JSString
*s
= IdToString(cx
, id
);
374 if (!Quote(cx
, scx
->sb
, s
) ||
375 !scx
->sb
.append(':') ||
376 !(scx
->gap
.empty() || scx
->sb
.append(' ')) ||
377 !Str(cx
, outputValue
, scx
))
383 if (wroteMember
&& !WriteIndent(cx
, scx
, scx
->depth
- 1))
386 return scx
->sb
.append('}');
389 /* ES5 15.12.3 JA. */
391 JA(JSContext
*cx
, HandleObject obj
, StringifyContext
*scx
)
394 * This method implements the JA algorithm in ES5 15.12.3, but:
396 * * The algorithm is somewhat reformulated to allow the final string to
397 * be streamed into a single buffer, rather than be created and copied
398 * into place incrementally as the ES5 algorithm specifies it. This
399 * requires moving portions of the Str call in 8a into this algorithm
400 * (and in JO as well).
404 AutoCycleDetector
detect(cx
, obj
);
407 if (detect
.foundCycle()) {
408 JS_ReportErrorNumber(cx
, js_GetErrorMessage
, nullptr, JSMSG_JSON_CYCLIC_VALUE
,
413 if (!scx
->sb
.append('['))
418 if (!GetLengthProperty(cx
, obj
, &length
))
423 /* Steps 4, 10b(i). */
424 if (!WriteIndent(cx
, scx
, scx
->depth
))
428 RootedValue
outputValue(cx
);
429 for (uint32_t i
= 0; i
< length
; i
++) {
431 * Steps 8a-8c. Again note how the call to the spec's Str method
432 * is broken up into getting the property, running it past toJSON
433 * and the replacer and maybe unboxing, and interpreting some
434 * values as |null| in separate steps.
436 if (!JSObject::getElement(cx
, obj
, obj
, i
, &outputValue
))
438 if (!PreprocessValue(cx
, obj
, i
, &outputValue
, scx
))
440 if (IsFilteredValue(outputValue
)) {
441 if (!scx
->sb
.append("null"))
444 if (!Str(cx
, outputValue
, scx
))
448 /* Steps 3, 4, 10b(i). */
449 if (i
< length
- 1) {
450 if (!scx
->sb
.append(','))
452 if (!WriteIndent(cx
, scx
, scx
->depth
))
457 /* Step 10(b)(iii). */
458 if (!WriteIndent(cx
, scx
, scx
->depth
- 1))
462 return scx
->sb
.append(']');
466 Str(JSContext
*cx
, const Value
&v
, StringifyContext
*scx
)
468 /* Step 11 must be handled by the caller. */
469 JS_ASSERT(!IsFilteredValue(v
));
471 JS_CHECK_RECURSION(cx
, return false);
474 * This method implements the Str algorithm in ES5 15.12.3, but:
476 * * We move property retrieval (step 1) into callers to stream the
477 * stringification process and avoid constantly copying strings.
478 * * We move the preprocessing in steps 2-4 into a helper function to
479 * allow both JO and JA to use this method. While JA could use it
480 * without this move, JO must omit any |undefined|-valued property per
481 * so it can't stream out a value using the Str method exactly as
483 * * We move step 11 into callers, again to ease streaming.
488 return Quote(cx
, scx
->sb
, v
.toString());
492 return scx
->sb
.append("null");
496 return v
.toBoolean() ? scx
->sb
.append("true") : scx
->sb
.append("false");
501 if (!IsFinite(v
.toDouble()))
502 return scx
->sb
.append("null");
505 return NumberValueToStringBuffer(cx
, v
, scx
->sb
);
509 JS_ASSERT(v
.isObject());
510 RootedObject
obj(cx
, &v
.toObject());
514 if (ObjectClassIs(obj
, ESClass_Array
, cx
))
515 ok
= JA(cx
, obj
, scx
);
517 ok
= JO(cx
, obj
, scx
);
525 js_Stringify(JSContext
*cx
, MutableHandleValue vp
, JSObject
*replacer_
, Value space_
,
528 RootedObject
replacer(cx
, replacer_
);
529 RootedValue
space(cx
, space_
);
532 AutoIdVector
propertyList(cx
);
534 if (replacer
->isCallable()) {
535 /* Step 4a(i): use replacer to transform values. */
536 } else if (ObjectClassIs(replacer
, ESClass_Array
, cx
)) {
538 * Step 4b: The spec algorithm is unhelpfully vague about the exact
539 * steps taken when the replacer is an array, regarding the exact
540 * sequence of [[Get]] calls for the array's elements, when its
541 * overall length is calculated, whether own or own plus inherited
542 * properties are considered, and so on. A rewrite was proposed in
543 * <https://mail.mozilla.org/pipermail/es5-discuss/2011-April/003976.html>,
544 * whose steps are copied below, and which are implemented here.
546 * i. Let PropertyList be an empty internal List.
547 * ii. Let len be the result of calling the [[Get]] internal
548 * method of replacer with the argument "length".
551 * 1. Let item be undefined.
552 * 2. Let v be the result of calling the [[Get]] internal
553 * method of replacer with the argument ToString(i).
554 * 3. If Type(v) is String then let item be v.
555 * 4. Else if Type(v) is Number then let item be ToString(v).
556 * 5. Else if Type(v) is Object then
557 * a. If the [[Class]] internal property of v is "String"
558 * or "Number" then let item be ToString(v).
559 * 6. If item is not undefined and item is not currently an
560 * element of PropertyList then,
561 * a. Append item to the end of PropertyList.
567 JS_ALWAYS_TRUE(GetLengthProperty(cx
, replacer
, &len
));
568 if (replacer
->is
<ArrayObject
>() && !replacer
->isIndexed())
569 len
= Min(len
, replacer
->getDenseInitializedLength());
571 // Cap the initial size to a moderately small value. This avoids
572 // ridiculous over-allocation if an array with bogusly-huge length
573 // is passed in. If we end up having to add elements past this
574 // size, the set will naturally resize to accommodate them.
575 const uint32_t MaxInitialSize
= 1024;
576 HashSet
<jsid
, JsidHasher
> idSet(cx
);
577 if (!idSet
.init(Min(len
, MaxInitialSize
)))
585 for (; i
< len
; i
++) {
586 if (!CheckForInterrupt(cx
))
589 /* Step 4b(iv)(2). */
590 if (!JSObject::getElement(cx
, replacer
, replacer
, i
, &v
))
595 /* Step 4b(iv)(4). */
597 if (v
.isNumber() && ValueFitsInInt32(v
, &n
) && INT_FITS_IN_JSID(n
)) {
600 if (!ValueToId
<CanGC
>(cx
, v
, &id
))
603 } else if (v
.isString() ||
604 IsObjectWithClass(v
, ESClass_String
, cx
) ||
605 IsObjectWithClass(v
, ESClass_Number
, cx
))
607 /* Step 4b(iv)(3), 4b(iv)(5). */
608 if (!ValueToId
<CanGC
>(cx
, v
, &id
))
614 /* Step 4b(iv)(6). */
615 HashSet
<jsid
, JsidHasher
>::AddPtr p
= idSet
.lookupForAdd(id
);
617 /* Step 4b(iv)(6)(a). */
618 if (!idSet
.add(p
, id
) || !propertyList
.append(id
))
628 if (space
.isObject()) {
629 RootedObject
spaceObj(cx
, &space
.toObject());
630 if (ObjectClassIs(spaceObj
, ESClass_Number
, cx
)) {
632 if (!ToNumber(cx
, space
, &d
))
634 space
= NumberValue(d
);
635 } else if (ObjectClassIs(spaceObj
, ESClass_String
, cx
)) {
636 JSString
*str
= ToStringSlow
<CanGC
>(cx
, space
);
639 space
= StringValue(str
);
643 StringBuffer
gap(cx
);
645 if (space
.isNumber()) {
648 JS_ALWAYS_TRUE(ToInteger(cx
, space
, &d
));
650 if (d
>= 1 && !gap
.appendN(' ', uint32_t(d
)))
652 } else if (space
.isString()) {
654 JSLinearString
*str
= space
.toString()->ensureLinear(cx
);
657 JS::Anchor
<JSString
*> anchor(str
);
658 size_t len
= Min(size_t(10), str
->length());
659 if (!gap
.appendSubstring(str
, 0, len
))
663 JS_ASSERT(gap
.empty());
667 RootedObject
wrapper(cx
, NewBuiltinClassInstance(cx
, &JSObject::class_
));
672 RootedId
emptyId(cx
, NameToId(cx
->names().empty
));
673 if (!DefineNativeProperty(cx
, wrapper
, emptyId
, vp
, JS_PropertyStub
, JS_StrictPropertyStub
,
680 StringifyContext
scx(cx
, sb
, gap
, replacer
, propertyList
);
681 if (!PreprocessValue(cx
, wrapper
, HandleId(emptyId
), vp
, &scx
))
683 if (IsFilteredValue(vp
))
686 return Str(cx
, vp
, &scx
);
689 /* ES5 15.12.2 Walk. */
691 Walk(JSContext
*cx
, HandleObject holder
, HandleId name
, HandleValue reviver
, MutableHandleValue vp
)
693 JS_CHECK_RECURSION(cx
, return false);
697 if (!JSObject::getGeneric(cx
, holder
, holder
, name
, &val
))
701 if (val
.isObject()) {
702 RootedObject
obj(cx
, &val
.toObject());
704 if (ObjectClassIs(obj
, ESClass_Array
, cx
)) {
707 if (!GetLengthProperty(cx
, obj
, &length
))
710 /* Step 2a(i), 2a(iii-iv). */
712 RootedValue
newElement(cx
);
713 for (uint32_t i
= 0; i
< length
; i
++) {
714 if (!IndexToId(cx
, i
, &id
))
717 /* Step 2a(iii)(1). */
718 if (!Walk(cx
, obj
, id
, reviver
, &newElement
))
721 if (newElement
.isUndefined()) {
722 /* Step 2a(iii)(2). */
724 if (!JSObject::deleteGeneric(cx
, obj
, id
, &succeeded
))
727 /* Step 2a(iii)(3). */
728 // XXX This definition should ignore success/failure, when
729 // our property-definition APIs indicate that.
730 if (!JSObject::defineGeneric(cx
, obj
, id
, newElement
))
736 AutoIdVector
keys(cx
);
737 if (!GetPropertyNames(cx
, obj
, JSITER_OWNONLY
, &keys
))
742 RootedValue
newElement(cx
);
743 for (size_t i
= 0, len
= keys
.length(); i
< len
; i
++) {
744 /* Step 2b(ii)(1). */
746 if (!Walk(cx
, obj
, id
, reviver
, &newElement
))
749 if (newElement
.isUndefined()) {
750 /* Step 2b(ii)(2). */
752 if (!JSObject::deleteGeneric(cx
, obj
, id
, &succeeded
))
755 /* Step 2b(ii)(3). */
756 // XXX This definition should ignore success/failure, when
757 // our property-definition APIs indicate that.
758 if (!JSObject::defineGeneric(cx
, obj
, id
, newElement
))
766 RootedString
key(cx
, IdToString(cx
, name
));
774 args
.setCallee(reviver
);
775 args
.setThis(ObjectValue(*holder
));
776 args
[0].setString(key
);
779 if (!Invoke(cx
, args
))
786 Revive(JSContext
*cx
, HandleValue reviver
, MutableHandleValue vp
)
788 RootedObject
obj(cx
, NewBuiltinClassInstance(cx
, &JSObject::class_
));
792 if (!JSObject::defineProperty(cx
, obj
, cx
->names().empty
, vp
))
795 Rooted
<jsid
> id(cx
, NameToId(cx
->names().empty
));
796 return Walk(cx
, obj
, id
, reviver
, vp
);
799 template <typename CharT
>
801 js::ParseJSONWithReviver(JSContext
*cx
, const mozilla::Range
<const CharT
> chars
, HandleValue reviver
,
802 MutableHandleValue vp
)
804 /* 15.12.2 steps 2-3. */
805 JSONParser
<CharT
> parser(cx
, chars
);
806 if (!parser
.parse(vp
))
809 /* 15.12.2 steps 4-5. */
810 if (IsCallable(reviver
))
811 return Revive(cx
, reviver
, vp
);
816 js::ParseJSONWithReviver(JSContext
*cx
, const mozilla::Range
<const Latin1Char
> chars
,
817 HandleValue reviver
, MutableHandleValue vp
);
820 js::ParseJSONWithReviver(JSContext
*cx
, const mozilla::Range
<const jschar
> chars
, HandleValue reviver
,
821 MutableHandleValue vp
);
825 json_toSource(JSContext
*cx
, unsigned argc
, Value
*vp
)
827 CallArgs args
= CallArgsFromVp(argc
, vp
);
828 args
.rval().setString(cx
->names().JSON
);
835 json_parse(JSContext
*cx
, unsigned argc
, Value
*vp
)
837 CallArgs args
= CallArgsFromVp(argc
, vp
);
840 JSString
*str
= (args
.length() >= 1)
841 ? ToString
<CanGC
>(cx
, args
[0])
842 : cx
->names().undefined
;
846 JSFlatString
*flat
= str
->ensureFlat(cx
);
850 JS::Anchor
<JSString
*> anchor(flat
);
852 AutoStableStringChars
flatChars(cx
);
853 if (!flatChars
.init(cx
, flat
))
856 RootedValue
reviver(cx
, args
.get(1));
859 return flatChars
.isLatin1()
860 ? ParseJSONWithReviver(cx
, flatChars
.latin1Range(), reviver
, args
.rval())
861 : ParseJSONWithReviver(cx
, flatChars
.twoByteRange(), reviver
, args
.rval());
866 json_stringify(JSContext
*cx
, unsigned argc
, Value
*vp
)
868 CallArgs args
= CallArgsFromVp(argc
, vp
);
869 RootedObject
replacer(cx
, args
.get(1).isObject() ? &args
[1].toObject() : nullptr);
870 RootedValue
value(cx
, args
.get(0));
871 RootedValue
space(cx
, args
.get(2));
874 if (!js_Stringify(cx
, &value
, replacer
, space
, sb
))
877 // XXX This can never happen to nsJSON.cpp, but the JSON object
878 // needs to support returning undefined. So this is a little awkward
879 // for the API, because we want to support streaming writers.
881 JSString
*str
= sb
.finishString();
884 args
.rval().setString(str
);
886 args
.rval().setUndefined();
892 static const JSFunctionSpec json_static_methods
[] = {
894 JS_FN(js_toSource_str
, json_toSource
, 0, 0),
896 JS_FN("parse", json_parse
, 2, 0),
897 JS_FN("stringify", json_stringify
, 3, 0),
902 js_InitJSONClass(JSContext
*cx
, HandleObject obj
)
904 Rooted
<GlobalObject
*> global(cx
, &obj
->as
<GlobalObject
>());
907 * JSON requires that Boolean.prototype.valueOf be created and stashed in a
908 * reserved slot on the global object; see js::BooleanGetPrimitiveValueSlow
909 * called from PreprocessValue above.
911 if (!GlobalObject::getOrCreateBooleanPrototype(cx
, global
))
914 RootedObject
proto(cx
, obj
->as
<GlobalObject
>().getOrCreateObjectPrototype(cx
));
915 RootedObject
JSON(cx
, NewObjectWithClassProto(cx
, &JSONClass
, proto
, global
, SingletonObject
));
919 if (!JS_DefineProperty(cx
, global
, js_JSON_str
, JSON
, 0,
920 JS_PropertyStub
, JS_StrictPropertyStub
))
923 if (!JS_DefineFunctions(cx
, JSON
, json_static_methods
))
926 global
->setConstructor(JSProto_JSON
, ObjectValue(*JSON
));