2 * Copyright (C) 2009 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #include "JSONObject.h"
29 #include "BooleanObject.h"
31 #include "ExceptionHelpers.h"
33 #include "LiteralParser.h"
34 #include "PropertyNameArray.h"
35 #include <wtf/MathExtras.h>
39 ASSERT_CLASS_FITS_IN_CELL(JSONObject
);
41 static JSValue JSC_HOST_CALL
JSONProtoFuncParse(ExecState
*, JSObject
*, JSValue
, const ArgList
&);
42 static JSValue JSC_HOST_CALL
JSONProtoFuncStringify(ExecState
*, JSObject
*, JSValue
, const ArgList
&);
46 #include "JSONObject.lut.h"
50 // PropertyNameForFunctionCall objects must be on the stack, since the JSValue that they create is not marked.
51 class PropertyNameForFunctionCall
{
53 PropertyNameForFunctionCall(const Identifier
&);
54 PropertyNameForFunctionCall(unsigned);
56 JSValue
value(ExecState
*) const;
59 const Identifier
* m_identifier
;
61 mutable JSValue m_value
;
64 class Stringifier
: Noncopyable
{
66 Stringifier(ExecState
*, JSValue replacer
, JSValue space
);
68 JSValue
stringify(JSValue
);
73 typedef UString StringBuilder
;
79 JSObject
* object() const { return m_object
; }
81 bool appendNextProperty(Stringifier
&, StringBuilder
&);
84 JSObject
* const m_object
;
89 RefPtr
<PropertyNameArrayData
> m_propertyNames
;
94 static void appendQuotedString(StringBuilder
&, const UString
&);
96 JSValue
toJSON(JSValue
, const PropertyNameForFunctionCall
&);
98 enum StringifyResult
{ StringifyFailed
, StringifySucceeded
, StringifyFailedDueToUndefinedValue
};
99 StringifyResult
appendStringifiedValue(StringBuilder
&, JSValue
, JSObject
* holder
, const PropertyNameForFunctionCall
&);
101 bool willIndent() const;
104 void startNewLine(StringBuilder
&) const;
106 Stringifier
* const m_nextStringifierToMark
;
107 ExecState
* const m_exec
;
108 const JSValue m_replacer
;
109 bool m_usingArrayReplacer
;
110 PropertyNameArray m_arrayReplacerPropertyNames
;
111 CallType m_replacerCallType
;
112 CallData m_replacerCallData
;
115 HashSet
<JSObject
*> m_holderCycleDetector
;
116 Vector
<Holder
, 16> m_holderStack
;
117 UString m_repeatedGap
;
121 // ------------------------------ helper functions --------------------------------
123 static inline JSValue
unwrapBoxedPrimitive(JSValue value
)
125 if (!value
.isObject())
127 if (!asObject(value
)->inherits(&NumberObject::info
) && !asObject(value
)->inherits(&StringObject::info
) && !asObject(value
)->inherits(&BooleanObject::info
))
129 return static_cast<JSWrapperObject
*>(asObject(value
))->internalValue();
132 static inline UString
gap(JSValue space
)
134 space
= unwrapBoxedPrimitive(space
);
136 // If the space value is a number, create a gap string with that number of spaces.
138 if (space
.getNumber(spaceCount
)) {
139 const int maxSpaceCount
= 100;
141 if (spaceCount
> maxSpaceCount
)
142 count
= maxSpaceCount
;
143 else if (!(spaceCount
> 0))
146 count
= static_cast<int>(spaceCount
);
147 UChar spaces
[maxSpaceCount
];
148 for (int i
= 0; i
< count
; ++i
)
150 return UString(spaces
, count
);
153 // If the space value is a string, use it as the gap string, otherwise use no gap string.
154 return space
.getString();
157 // ------------------------------ PropertyNameForFunctionCall --------------------------------
159 inline PropertyNameForFunctionCall::PropertyNameForFunctionCall(const Identifier
& identifier
)
160 : m_identifier(&identifier
)
164 inline PropertyNameForFunctionCall::PropertyNameForFunctionCall(unsigned number
)
170 JSValue
PropertyNameForFunctionCall::value(ExecState
* exec
) const
174 m_value
= jsString(exec
, m_identifier
->ustring());
176 m_value
= jsNumber(exec
, m_number
);
181 // ------------------------------ Stringifier --------------------------------
183 Stringifier::Stringifier(ExecState
* exec
, JSValue replacer
, JSValue space
)
184 : m_nextStringifierToMark(exec
->globalData().firstStringifierToMark
)
186 , m_replacer(replacer
)
187 , m_usingArrayReplacer(false)
188 , m_arrayReplacerPropertyNames(exec
)
189 , m_replacerCallType(CallTypeNone
)
192 exec
->globalData().firstStringifierToMark
= this;
194 if (!m_replacer
.isObject())
197 if (asObject(m_replacer
)->inherits(&JSArray::info
)) {
198 m_usingArrayReplacer
= true;
199 JSObject
* array
= asObject(m_replacer
);
200 unsigned length
= array
->get(exec
, exec
->globalData().propertyNames
->length
).toUInt32(exec
);
201 for (unsigned i
= 0; i
< length
; ++i
) {
202 JSValue name
= array
->get(exec
, i
);
203 if (exec
->hadException())
205 UString propertyName
;
206 if (!name
.getString(propertyName
))
208 if (exec
->hadException())
210 m_arrayReplacerPropertyNames
.add(Identifier(exec
, propertyName
));
215 m_replacerCallType
= asObject(m_replacer
)->getCallData(m_replacerCallData
);
218 Stringifier::~Stringifier()
220 ASSERT(m_exec
->globalData().firstStringifierToMark
== this);
221 m_exec
->globalData().firstStringifierToMark
= m_nextStringifierToMark
;
224 void Stringifier::mark()
226 for (Stringifier
* stringifier
= this; stringifier
; stringifier
= stringifier
->m_nextStringifierToMark
) {
227 size_t size
= m_holderStack
.size();
228 for (size_t i
= 0; i
< size
; ++i
) {
229 JSObject
* object
= m_holderStack
[i
].object();
230 if (!object
->marked())
236 JSValue
Stringifier::stringify(JSValue value
)
238 JSObject
* object
= constructEmptyObject(m_exec
);
239 if (m_exec
->hadException())
242 PropertyNameForFunctionCall
emptyPropertyName(m_exec
->globalData().propertyNames
->emptyIdentifier
);
243 object
->putDirect(m_exec
->globalData().propertyNames
->emptyIdentifier
, value
);
245 StringBuilder result
;
246 if (appendStringifiedValue(result
, value
, object
, emptyPropertyName
) != StringifySucceeded
)
247 return jsUndefined();
248 if (m_exec
->hadException())
251 return jsString(m_exec
, result
);
254 void Stringifier::appendQuotedString(StringBuilder
& builder
, const UString
& value
)
256 int length
= value
.size();
258 // String length plus 2 for quote marks plus 8 so we can accomodate a few escaped characters.
259 builder
.reserveCapacity(builder
.size() + length
+ 2 + 8);
263 const UChar
* data
= value
.data();
264 for (int i
= 0; i
< length
; ++i
) {
266 while (i
< length
&& (data
[i
] > 0x1F && data
[i
] != '"' && data
[i
] != '\\'))
268 builder
.append(data
+ start
, i
- start
);
273 builder
.append('\\');
277 builder
.append('\\');
281 builder
.append('\\');
285 builder
.append('\\');
289 builder
.append('\\');
293 builder
.append('\\');
297 builder
.append('\\');
298 builder
.append('\\');
301 static const char hexDigits
[] = "0123456789abcdef";
303 UChar hex
[] = { '\\', 'u', hexDigits
[(ch
>> 12) & 0xF], hexDigits
[(ch
>> 8) & 0xF], hexDigits
[(ch
>> 4) & 0xF], hexDigits
[ch
& 0xF] };
304 builder
.append(hex
, sizeof(hex
) / sizeof(UChar
));
312 inline JSValue
Stringifier::toJSON(JSValue value
, const PropertyNameForFunctionCall
& propertyName
)
314 ASSERT(!m_exec
->hadException());
315 if (!value
.isObject() || !asObject(value
)->hasProperty(m_exec
, m_exec
->globalData().propertyNames
->toJSON
))
318 JSValue toJSONFunction
= asObject(value
)->get(m_exec
, m_exec
->globalData().propertyNames
->toJSON
);
319 if (m_exec
->hadException())
322 if (!toJSONFunction
.isObject())
325 JSObject
* object
= asObject(toJSONFunction
);
327 CallType callType
= object
->getCallData(callData
);
328 if (callType
== CallTypeNone
)
331 JSValue list
[] = { propertyName
.value(m_exec
) };
332 ArgList
args(list
, sizeof(list
) / sizeof(JSValue
));
333 return call(m_exec
, object
, callType
, callData
, value
, args
);
336 Stringifier::StringifyResult
Stringifier::appendStringifiedValue(StringBuilder
& builder
, JSValue value
, JSObject
* holder
, const PropertyNameForFunctionCall
& propertyName
)
338 // Call the toJSON function.
339 value
= toJSON(value
, propertyName
);
340 if (m_exec
->hadException())
341 return StringifyFailed
;
342 if (value
.isUndefined() && !holder
->inherits(&JSArray::info
))
343 return StringifyFailedDueToUndefinedValue
;
345 // Call the replacer function.
346 if (m_replacerCallType
!= CallTypeNone
) {
347 JSValue list
[] = { propertyName
.value(m_exec
), value
};
348 ArgList
args(list
, sizeof(list
) / sizeof(JSValue
));
349 value
= call(m_exec
, m_replacer
, m_replacerCallType
, m_replacerCallData
, holder
, args
);
350 if (m_exec
->hadException())
351 return StringifyFailed
;
354 if (value
.isNull()) {
355 builder
.append("null");
356 return StringifySucceeded
;
359 value
= unwrapBoxedPrimitive(value
);
361 if (value
.isBoolean()) {
362 builder
.append(value
.getBoolean() ? "true" : "false");
363 return StringifySucceeded
;
367 if (value
.getString(stringValue
)) {
368 appendQuotedString(builder
, stringValue
);
369 return StringifySucceeded
;
373 if (value
.getNumber(numericValue
)) {
374 if (!isfinite(numericValue
))
375 builder
.append("null");
377 builder
.append(UString::from(numericValue
));
378 return StringifySucceeded
;
381 if (!value
.isObject())
382 return StringifyFailed
;
384 JSObject
* object
= asObject(value
);
386 // Handle cycle detection, and put the holder on the stack.
387 if (!m_holderCycleDetector
.add(object
).second
) {
388 throwError(m_exec
, TypeError
);
389 return StringifyFailed
;
391 bool holderStackWasEmpty
= m_holderStack
.isEmpty();
392 m_holderStack
.append(object
);
393 if (!holderStackWasEmpty
)
394 return StringifySucceeded
;
396 // If this is the outermost call, then loop to handle everything on the holder stack.
397 TimeoutChecker
localTimeoutChecker(m_exec
->globalData().timeoutChecker
);
398 localTimeoutChecker
.reset();
399 unsigned tickCount
= localTimeoutChecker
.ticksUntilNextCheck();
401 while (m_holderStack
.last().appendNextProperty(*this, builder
)) {
402 if (m_exec
->hadException())
403 return StringifyFailed
;
405 if (localTimeoutChecker
.didTimeOut(m_exec
)) {
406 m_exec
->setException(createInterruptedExecutionException(&m_exec
->globalData()));
407 return StringifyFailed
;
409 tickCount
= localTimeoutChecker
.ticksUntilNextCheck();
412 m_holderCycleDetector
.remove(m_holderStack
.last().object());
413 m_holderStack
.removeLast();
414 } while (!m_holderStack
.isEmpty());
415 return StringifySucceeded
;
418 inline bool Stringifier::willIndent() const
420 return !m_gap
.isEmpty();
423 inline void Stringifier::indent()
425 // Use a single shared string, m_repeatedGap, so we don't keep allocating new ones as we indent and unindent.
426 int newSize
= m_indent
.size() + m_gap
.size();
427 if (newSize
> m_repeatedGap
.size())
428 m_repeatedGap
.append(m_gap
);
429 ASSERT(newSize
<= m_repeatedGap
.size());
430 m_indent
= m_repeatedGap
.substr(0, newSize
);
433 inline void Stringifier::unindent()
435 ASSERT(m_indent
.size() >= m_gap
.size());
436 m_indent
= m_repeatedGap
.substr(0, m_indent
.size() - m_gap
.size());
439 inline void Stringifier::startNewLine(StringBuilder
& builder
) const
443 builder
.append('\n');
444 builder
.append(m_indent
);
447 inline Stringifier::Holder::Holder(JSObject
* object
)
449 , m_isArray(object
->inherits(&JSArray::info
))
454 bool Stringifier::Holder::appendNextProperty(Stringifier
& stringifier
, StringBuilder
& builder
)
456 ASSERT(m_index
<= m_size
);
458 ExecState
* exec
= stringifier
.m_exec
;
460 // First time through, initialize.
463 m_isJSArray
= isJSArray(&exec
->globalData(), m_object
);
464 m_size
= m_object
->get(exec
, exec
->globalData().propertyNames
->length
).toUInt32(exec
);
467 if (stringifier
.m_usingArrayReplacer
)
468 m_propertyNames
= stringifier
.m_arrayReplacerPropertyNames
.data();
470 PropertyNameArray
objectPropertyNames(exec
);
471 m_object
->getPropertyNames(exec
, objectPropertyNames
);
472 m_propertyNames
= objectPropertyNames
.releaseData();
474 m_size
= m_propertyNames
->propertyNameVector().size();
477 stringifier
.indent();
480 // Last time through, finish up and return false.
481 if (m_index
== m_size
) {
482 stringifier
.unindent();
483 if (m_size
&& builder
[builder
.size() - 1] != '{')
484 stringifier
.startNewLine(builder
);
485 builder
.append(m_isArray
? ']' : '}');
489 // Handle a single element of the array or object.
490 unsigned index
= m_index
++;
491 unsigned rollBackPoint
= 0;
492 StringifyResult stringifyResult
;
496 if (m_isJSArray
&& asArray(m_object
)->canGetIndex(index
))
497 value
= asArray(m_object
)->getIndex(index
);
499 PropertySlot
slot(m_object
);
500 if (!m_object
->getOwnPropertySlot(exec
, index
, slot
))
502 if (exec
->hadException())
504 value
= slot
.getValue(exec
, index
);
507 // Append the separator string.
510 stringifier
.startNewLine(builder
);
512 // Append the stringified value.
513 stringifyResult
= stringifier
.appendStringifiedValue(builder
, value
, m_object
, index
);
516 PropertySlot
slot(m_object
);
517 Identifier
& propertyName
= m_propertyNames
->propertyNameVector()[index
];
518 if (!m_object
->getOwnPropertySlot(exec
, propertyName
, slot
))
520 JSValue value
= slot
.getValue(exec
, propertyName
);
521 if (exec
->hadException())
524 rollBackPoint
= builder
.size();
526 // Append the separator string.
527 if (builder
[rollBackPoint
- 1] != '{')
529 stringifier
.startNewLine(builder
);
531 // Append the property name.
532 appendQuotedString(builder
, propertyName
.ustring());
534 if (stringifier
.willIndent())
537 // Append the stringified value.
538 stringifyResult
= stringifier
.appendStringifiedValue(builder
, value
, m_object
, propertyName
);
541 // From this point on, no access to the this pointer or to any members, because the
542 // Holder object may have moved if the call to stringify pushed a new Holder onto
545 switch (stringifyResult
) {
546 case StringifyFailed
:
547 builder
.append("null");
549 case StringifySucceeded
:
551 case StringifyFailedDueToUndefinedValue
:
552 // This only occurs when get an undefined value for an object property.
553 // In this case we don't want the separator and property name that we
554 // already appended, so roll back.
555 builder
= builder
.substr(0, rollBackPoint
);
562 // ------------------------------ JSONObject --------------------------------
564 const ClassInfo
JSONObject::info
= { "JSON", 0, 0, ExecState::jsonTable
};
566 /* Source for JSONObject.lut.h
568 parse JSONProtoFuncParse DontEnum|Function 1
569 stringify JSONProtoFuncStringify DontEnum|Function 1
575 bool JSONObject::getOwnPropertySlot(ExecState
* exec
, const Identifier
& propertyName
, PropertySlot
& slot
)
577 const HashEntry
* entry
= ExecState::jsonTable(exec
)->entry(exec
, propertyName
);
579 return JSObject::getOwnPropertySlot(exec
, propertyName
, slot
);
581 ASSERT(entry
->attributes() & Function
);
582 setUpStaticFunctionSlot(exec
, entry
, this, propertyName
, slot
);
586 void JSONObject::markStringifiers(Stringifier
* stringifier
)
593 Walker(ExecState
* exec
, JSObject
* function
, CallType callType
, CallData callData
)
595 , m_function(function
)
596 , m_callType(callType
)
597 , m_callData(callData
)
600 JSValue
walk(JSValue unfiltered
);
602 JSValue
callReviver(JSValue property
, JSValue unfiltered
)
604 JSValue args
[] = { property
, unfiltered
};
605 ArgList
argList(args
, 2);
606 return call(m_exec
, m_function
, m_callType
, m_callData
, jsNull(), argList
);
612 JSObject
* m_function
;
617 enum WalkerState
{ StateUnknown
, ArrayStartState
, ArrayStartVisitMember
, ArrayEndVisitMember
,
618 ObjectStartState
, ObjectStartVisitMember
, ObjectEndVisitMember
};
619 NEVER_INLINE JSValue
Walker::walk(JSValue unfiltered
)
621 Vector
<PropertyNameArray
, 16> propertyStack
;
622 Vector
<uint32_t, 16> indexStack
;
623 Vector
<JSObject
*, 16> objectStack
;
624 Vector
<JSArray
*, 16> arrayStack
;
626 Vector
<WalkerState
, 16> stateStack
;
627 WalkerState state
= StateUnknown
;
628 JSValue inValue
= unfiltered
;
629 JSValue outValue
= jsNull();
633 case ArrayStartState
: {
634 ASSERT(inValue
.isObject());
635 ASSERT(isJSArray(&m_exec
->globalData(), asObject(inValue
)));
636 JSArray
* array
= asArray(inValue
);
637 arrayStack
.append(array
);
638 indexStack
.append(0);
641 arrayStartVisitMember
:
642 case ArrayStartVisitMember
: {
643 JSArray
* array
= arrayStack
.last();
644 uint32_t index
= indexStack
.last();
645 if (index
== array
->length()) {
647 arrayStack
.removeLast();
648 indexStack
.removeLast();
651 inValue
= array
->getIndex(index
);
652 if (inValue
.isObject()) {
653 stateStack
.append(ArrayEndVisitMember
);
659 case ArrayEndVisitMember
: {
660 JSArray
* array
= arrayStack
.last();
661 array
->setIndex(indexStack
.last(), callReviver(jsString(m_exec
, UString::from(indexStack
.last())), outValue
));
662 if (m_exec
->hadException())
665 goto arrayStartVisitMember
;
668 case ObjectStartState
: {
669 ASSERT(inValue
.isObject());
670 ASSERT(!isJSArray(&m_exec
->globalData(), asObject(inValue
)));
671 JSObject
* object
= asObject(inValue
);
672 objectStack
.append(object
);
673 indexStack
.append(0);
674 propertyStack
.append(PropertyNameArray(m_exec
));
675 object
->getPropertyNames(m_exec
, propertyStack
.last());
678 objectStartVisitMember
:
679 case ObjectStartVisitMember
: {
680 JSObject
* object
= objectStack
.last();
681 uint32_t index
= indexStack
.last();
682 PropertyNameArray
& properties
= propertyStack
.last();
683 if (index
== properties
.size()) {
685 objectStack
.removeLast();
686 indexStack
.removeLast();
687 propertyStack
.removeLast();
691 object
->getOwnPropertySlot(m_exec
, properties
[index
], slot
);
692 inValue
= slot
.getValue(m_exec
, properties
[index
]);
693 ASSERT(!m_exec
->hadException());
694 if (inValue
.isObject()) {
695 stateStack
.append(ObjectEndVisitMember
);
701 case ObjectEndVisitMember
: {
702 JSObject
* object
= objectStack
.last();
703 Identifier prop
= propertyStack
.last()[indexStack
.last()];
704 PutPropertySlot slot
;
705 object
->put(m_exec
, prop
, callReviver(jsString(m_exec
, prop
.ustring()), outValue
), slot
);
706 if (m_exec
->hadException())
709 goto objectStartVisitMember
;
713 if (!inValue
.isObject()) {
717 if (isJSArray(&m_exec
->globalData(), asObject(inValue
)))
718 goto arrayStartState
;
719 goto objectStartState
;
721 if (stateStack
.isEmpty())
723 state
= stateStack
.last();
724 stateStack
.removeLast();
726 return callReviver(jsEmptyString(m_exec
), outValue
);
729 // ECMA-262 v5 15.12.2
730 JSValue JSC_HOST_CALL
JSONProtoFuncParse(ExecState
* exec
, JSObject
*, JSValue
, const ArgList
& args
)
733 return throwError(exec
, GeneralError
, "JSON.parse requires at least one parameter");
734 JSValue value
= args
.at(0);
735 UString source
= value
.toString(exec
);
736 if (exec
->hadException())
739 LiteralParser
jsonParser(exec
, source
, LiteralParser::StrictJSON
);
740 JSValue unfiltered
= jsonParser
.tryLiteralParse();
742 return throwError(exec
, SyntaxError
, "Unable to parse JSON string");
747 JSValue function
= args
.at(1);
749 CallType callType
= function
.getCallData(callData
);
750 if (callType
== CallTypeNone
)
752 return Walker(exec
, asObject(function
), callType
, callData
).walk(unfiltered
);
755 // ECMA-262 v5 15.12.3
756 JSValue JSC_HOST_CALL
JSONProtoFuncStringify(ExecState
* exec
, JSObject
*, JSValue
, const ArgList
& args
)
759 return throwError(exec
, GeneralError
, "No input to stringify");
760 JSValue value
= args
.at(0);
761 JSValue replacer
= args
.at(1);
762 JSValue space
= args
.at(2);
763 return Stringifier(exec
, replacer
, space
).stringify(value
);