|
1 /* |
|
2 * Copyright (C) 2009 Apple Inc. All rights reserved. |
|
3 * |
|
4 * Redistribution and use in source and binary forms, with or without |
|
5 * modification, are permitted provided that the following conditions |
|
6 * are met: |
|
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. |
|
12 * |
|
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. |
|
24 */ |
|
25 |
|
26 #include "config.h" |
|
27 #include "JSONObject.h" |
|
28 |
|
29 #include "BooleanObject.h" |
|
30 #include "Error.h" |
|
31 #include "ExceptionHelpers.h" |
|
32 #include "JSArray.h" |
|
33 #include "JSGlobalObject.h" |
|
34 #include "LiteralParser.h" |
|
35 #include "Lookup.h" |
|
36 #include "PropertyNameArray.h" |
|
37 #include "StringBuilder.h" |
|
38 #include <wtf/MathExtras.h> |
|
39 |
|
40 namespace JSC { |
|
41 |
|
42 ASSERT_CLASS_FITS_IN_CELL(JSONObject); |
|
43 |
|
44 static EncodedJSValue JSC_HOST_CALL JSONProtoFuncParse(ExecState*); |
|
45 static EncodedJSValue JSC_HOST_CALL JSONProtoFuncStringify(ExecState*); |
|
46 |
|
47 } |
|
48 |
|
49 #include "JSONObject.lut.h" |
|
50 |
|
51 namespace JSC { |
|
52 |
|
53 JSONObject::JSONObject(JSGlobalObject* globalObject, NonNullPassRefPtr<Structure> structure) |
|
54 : JSObjectWithGlobalObject(globalObject, structure) |
|
55 { |
|
56 } |
|
57 |
|
58 // PropertyNameForFunctionCall objects must be on the stack, since the JSValue that they create is not marked. |
|
59 class PropertyNameForFunctionCall { |
|
60 public: |
|
61 PropertyNameForFunctionCall(const Identifier&); |
|
62 PropertyNameForFunctionCall(unsigned); |
|
63 |
|
64 JSValue value(ExecState*) const; |
|
65 |
|
66 private: |
|
67 const Identifier* m_identifier; |
|
68 unsigned m_number; |
|
69 mutable JSValue m_value; |
|
70 }; |
|
71 |
|
72 class Stringifier : public Noncopyable { |
|
73 public: |
|
74 Stringifier(ExecState*, JSValue replacer, JSValue space); |
|
75 ~Stringifier(); |
|
76 JSValue stringify(JSValue); |
|
77 |
|
78 void markAggregate(MarkStack&); |
|
79 |
|
80 private: |
|
81 class Holder { |
|
82 public: |
|
83 Holder(JSObject*); |
|
84 |
|
85 JSObject* object() const { return m_object; } |
|
86 |
|
87 bool appendNextProperty(Stringifier&, StringBuilder&); |
|
88 |
|
89 private: |
|
90 JSObject* const m_object; |
|
91 const bool m_isArray; |
|
92 bool m_isJSArray; |
|
93 unsigned m_index; |
|
94 unsigned m_size; |
|
95 RefPtr<PropertyNameArrayData> m_propertyNames; |
|
96 }; |
|
97 |
|
98 friend class Holder; |
|
99 |
|
100 static void appendQuotedString(StringBuilder&, const UString&); |
|
101 |
|
102 JSValue toJSON(JSValue, const PropertyNameForFunctionCall&); |
|
103 |
|
104 enum StringifyResult { StringifyFailed, StringifySucceeded, StringifyFailedDueToUndefinedValue }; |
|
105 StringifyResult appendStringifiedValue(StringBuilder&, JSValue, JSObject* holder, const PropertyNameForFunctionCall&); |
|
106 |
|
107 bool willIndent() const; |
|
108 void indent(); |
|
109 void unindent(); |
|
110 void startNewLine(StringBuilder&) const; |
|
111 |
|
112 Stringifier* const m_nextStringifierToMark; |
|
113 ExecState* const m_exec; |
|
114 const JSValue m_replacer; |
|
115 bool m_usingArrayReplacer; |
|
116 PropertyNameArray m_arrayReplacerPropertyNames; |
|
117 CallType m_replacerCallType; |
|
118 CallData m_replacerCallData; |
|
119 const UString m_gap; |
|
120 |
|
121 HashSet<JSObject*> m_holderCycleDetector; |
|
122 Vector<Holder, 16> m_holderStack; |
|
123 UString m_repeatedGap; |
|
124 UString m_indent; |
|
125 }; |
|
126 |
|
127 // ------------------------------ helper functions -------------------------------- |
|
128 |
|
129 static inline JSValue unwrapBoxedPrimitive(ExecState* exec, JSValue value) |
|
130 { |
|
131 if (!value.isObject()) |
|
132 return value; |
|
133 JSObject* object = asObject(value); |
|
134 if (object->inherits(&NumberObject::info)) |
|
135 return jsNumber(exec, object->toNumber(exec)); |
|
136 if (object->inherits(&StringObject::info)) |
|
137 return jsString(exec, object->toString(exec)); |
|
138 if (object->inherits(&BooleanObject::info)) |
|
139 return object->toPrimitive(exec); |
|
140 return value; |
|
141 } |
|
142 |
|
143 static inline UString gap(ExecState* exec, JSValue space) |
|
144 { |
|
145 const unsigned maxGapLength = 10; |
|
146 space = unwrapBoxedPrimitive(exec, space); |
|
147 |
|
148 // If the space value is a number, create a gap string with that number of spaces. |
|
149 double spaceCount; |
|
150 if (space.getNumber(spaceCount)) { |
|
151 int count; |
|
152 if (spaceCount > maxGapLength) |
|
153 count = maxGapLength; |
|
154 else if (!(spaceCount > 0)) |
|
155 count = 0; |
|
156 else |
|
157 count = static_cast<int>(spaceCount); |
|
158 UChar spaces[maxGapLength]; |
|
159 for (int i = 0; i < count; ++i) |
|
160 spaces[i] = ' '; |
|
161 return UString(spaces, count); |
|
162 } |
|
163 |
|
164 // If the space value is a string, use it as the gap string, otherwise use no gap string. |
|
165 UString spaces = space.getString(exec); |
|
166 if (spaces.size() > maxGapLength) { |
|
167 spaces = spaces.substr(0, maxGapLength); |
|
168 } |
|
169 return spaces; |
|
170 } |
|
171 |
|
172 // ------------------------------ PropertyNameForFunctionCall -------------------------------- |
|
173 |
|
174 inline PropertyNameForFunctionCall::PropertyNameForFunctionCall(const Identifier& identifier) |
|
175 : m_identifier(&identifier) |
|
176 { |
|
177 } |
|
178 |
|
179 inline PropertyNameForFunctionCall::PropertyNameForFunctionCall(unsigned number) |
|
180 : m_identifier(0) |
|
181 , m_number(number) |
|
182 { |
|
183 } |
|
184 |
|
185 JSValue PropertyNameForFunctionCall::value(ExecState* exec) const |
|
186 { |
|
187 if (!m_value) { |
|
188 if (m_identifier) |
|
189 m_value = jsString(exec, m_identifier->ustring()); |
|
190 else |
|
191 m_value = jsNumber(exec, m_number); |
|
192 } |
|
193 return m_value; |
|
194 } |
|
195 |
|
196 // ------------------------------ Stringifier -------------------------------- |
|
197 |
|
198 Stringifier::Stringifier(ExecState* exec, JSValue replacer, JSValue space) |
|
199 : m_nextStringifierToMark(exec->globalData().firstStringifierToMark) |
|
200 , m_exec(exec) |
|
201 , m_replacer(replacer) |
|
202 , m_usingArrayReplacer(false) |
|
203 , m_arrayReplacerPropertyNames(exec) |
|
204 , m_replacerCallType(CallTypeNone) |
|
205 , m_gap(gap(exec, space)) |
|
206 { |
|
207 exec->globalData().firstStringifierToMark = this; |
|
208 |
|
209 if (!m_replacer.isObject()) |
|
210 return; |
|
211 |
|
212 if (asObject(m_replacer)->inherits(&JSArray::info)) { |
|
213 m_usingArrayReplacer = true; |
|
214 JSObject* array = asObject(m_replacer); |
|
215 unsigned length = array->get(exec, exec->globalData().propertyNames->length).toUInt32(exec); |
|
216 for (unsigned i = 0; i < length; ++i) { |
|
217 JSValue name = array->get(exec, i); |
|
218 if (exec->hadException()) |
|
219 break; |
|
220 |
|
221 UString propertyName; |
|
222 if (name.getString(exec, propertyName)) { |
|
223 m_arrayReplacerPropertyNames.add(Identifier(exec, propertyName)); |
|
224 continue; |
|
225 } |
|
226 |
|
227 double value = 0; |
|
228 if (name.getNumber(value)) { |
|
229 m_arrayReplacerPropertyNames.add(Identifier::from(exec, value)); |
|
230 continue; |
|
231 } |
|
232 |
|
233 if (name.isObject()) { |
|
234 if (!asObject(name)->inherits(&NumberObject::info) && !asObject(name)->inherits(&StringObject::info)) |
|
235 continue; |
|
236 propertyName = name.toString(exec); |
|
237 if (exec->hadException()) |
|
238 break; |
|
239 m_arrayReplacerPropertyNames.add(Identifier(exec, propertyName)); |
|
240 } |
|
241 } |
|
242 return; |
|
243 } |
|
244 |
|
245 m_replacerCallType = asObject(m_replacer)->getCallData(m_replacerCallData); |
|
246 } |
|
247 |
|
248 Stringifier::~Stringifier() |
|
249 { |
|
250 ASSERT(m_exec->globalData().firstStringifierToMark == this); |
|
251 m_exec->globalData().firstStringifierToMark = m_nextStringifierToMark; |
|
252 } |
|
253 |
|
254 void Stringifier::markAggregate(MarkStack& markStack) |
|
255 { |
|
256 for (Stringifier* stringifier = this; stringifier; stringifier = stringifier->m_nextStringifierToMark) { |
|
257 size_t size = m_holderStack.size(); |
|
258 for (size_t i = 0; i < size; ++i) |
|
259 markStack.append(m_holderStack[i].object()); |
|
260 } |
|
261 } |
|
262 |
|
263 JSValue Stringifier::stringify(JSValue value) |
|
264 { |
|
265 JSObject* object = constructEmptyObject(m_exec); |
|
266 if (m_exec->hadException()) |
|
267 return jsNull(); |
|
268 |
|
269 PropertyNameForFunctionCall emptyPropertyName(m_exec->globalData().propertyNames->emptyIdentifier); |
|
270 object->putDirect(m_exec->globalData().propertyNames->emptyIdentifier, value); |
|
271 |
|
272 StringBuilder result; |
|
273 if (appendStringifiedValue(result, value, object, emptyPropertyName) != StringifySucceeded) |
|
274 return jsUndefined(); |
|
275 if (m_exec->hadException()) |
|
276 return jsNull(); |
|
277 |
|
278 return jsString(m_exec, result.build()); |
|
279 } |
|
280 |
|
281 void Stringifier::appendQuotedString(StringBuilder& builder, const UString& value) |
|
282 { |
|
283 int length = value.size(); |
|
284 |
|
285 // String length plus 2 for quote marks plus 8 so we can accomodate a few escaped characters. |
|
286 builder.reserveCapacity(builder.size() + length + 2 + 8); |
|
287 |
|
288 builder.append('"'); |
|
289 |
|
290 const UChar* data = value.data(); |
|
291 for (int i = 0; i < length; ++i) { |
|
292 int start = i; |
|
293 while (i < length && (data[i] > 0x1F && data[i] != '"' && data[i] != '\\')) |
|
294 ++i; |
|
295 builder.append(data + start, i - start); |
|
296 if (i >= length) |
|
297 break; |
|
298 switch (data[i]) { |
|
299 case '\t': |
|
300 builder.append('\\'); |
|
301 builder.append('t'); |
|
302 break; |
|
303 case '\r': |
|
304 builder.append('\\'); |
|
305 builder.append('r'); |
|
306 break; |
|
307 case '\n': |
|
308 builder.append('\\'); |
|
309 builder.append('n'); |
|
310 break; |
|
311 case '\f': |
|
312 builder.append('\\'); |
|
313 builder.append('f'); |
|
314 break; |
|
315 case '\b': |
|
316 builder.append('\\'); |
|
317 builder.append('b'); |
|
318 break; |
|
319 case '"': |
|
320 builder.append('\\'); |
|
321 builder.append('"'); |
|
322 break; |
|
323 case '\\': |
|
324 builder.append('\\'); |
|
325 builder.append('\\'); |
|
326 break; |
|
327 default: |
|
328 static const char hexDigits[] = "0123456789abcdef"; |
|
329 UChar ch = data[i]; |
|
330 UChar hex[] = { '\\', 'u', hexDigits[(ch >> 12) & 0xF], hexDigits[(ch >> 8) & 0xF], hexDigits[(ch >> 4) & 0xF], hexDigits[ch & 0xF] }; |
|
331 builder.append(hex, sizeof(hex) / sizeof(UChar)); |
|
332 break; |
|
333 } |
|
334 } |
|
335 |
|
336 builder.append('"'); |
|
337 } |
|
338 |
|
339 inline JSValue Stringifier::toJSON(JSValue value, const PropertyNameForFunctionCall& propertyName) |
|
340 { |
|
341 ASSERT(!m_exec->hadException()); |
|
342 if (!value.isObject() || !asObject(value)->hasProperty(m_exec, m_exec->globalData().propertyNames->toJSON)) |
|
343 return value; |
|
344 |
|
345 JSValue toJSONFunction = asObject(value)->get(m_exec, m_exec->globalData().propertyNames->toJSON); |
|
346 if (m_exec->hadException()) |
|
347 return jsNull(); |
|
348 |
|
349 if (!toJSONFunction.isObject()) |
|
350 return value; |
|
351 |
|
352 JSObject* object = asObject(toJSONFunction); |
|
353 CallData callData; |
|
354 CallType callType = object->getCallData(callData); |
|
355 if (callType == CallTypeNone) |
|
356 return value; |
|
357 |
|
358 JSValue list[] = { propertyName.value(m_exec) }; |
|
359 ArgList args(list, sizeof(list) / sizeof(JSValue)); |
|
360 return call(m_exec, object, callType, callData, value, args); |
|
361 } |
|
362 |
|
363 Stringifier::StringifyResult Stringifier::appendStringifiedValue(StringBuilder& builder, JSValue value, JSObject* holder, const PropertyNameForFunctionCall& propertyName) |
|
364 { |
|
365 // Call the toJSON function. |
|
366 value = toJSON(value, propertyName); |
|
367 if (m_exec->hadException()) |
|
368 return StringifyFailed; |
|
369 |
|
370 // Call the replacer function. |
|
371 if (m_replacerCallType != CallTypeNone) { |
|
372 JSValue list[] = { propertyName.value(m_exec), value }; |
|
373 ArgList args(list, sizeof(list) / sizeof(JSValue)); |
|
374 value = call(m_exec, m_replacer, m_replacerCallType, m_replacerCallData, holder, args); |
|
375 if (m_exec->hadException()) |
|
376 return StringifyFailed; |
|
377 } |
|
378 |
|
379 if (value.isUndefined() && !holder->inherits(&JSArray::info)) |
|
380 return StringifyFailedDueToUndefinedValue; |
|
381 |
|
382 if (value.isNull()) { |
|
383 builder.append("null"); |
|
384 return StringifySucceeded; |
|
385 } |
|
386 |
|
387 value = unwrapBoxedPrimitive(m_exec, value); |
|
388 |
|
389 if (m_exec->hadException()) |
|
390 return StringifyFailed; |
|
391 |
|
392 if (value.isBoolean()) { |
|
393 builder.append(value.getBoolean() ? "true" : "false"); |
|
394 return StringifySucceeded; |
|
395 } |
|
396 |
|
397 UString stringValue; |
|
398 if (value.getString(m_exec, stringValue)) { |
|
399 appendQuotedString(builder, stringValue); |
|
400 return StringifySucceeded; |
|
401 } |
|
402 |
|
403 double numericValue; |
|
404 if (value.getNumber(numericValue)) { |
|
405 if (!isfinite(numericValue)) |
|
406 builder.append("null"); |
|
407 else |
|
408 builder.append(UString::from(numericValue)); |
|
409 return StringifySucceeded; |
|
410 } |
|
411 |
|
412 if (!value.isObject()) |
|
413 return StringifyFailed; |
|
414 |
|
415 JSObject* object = asObject(value); |
|
416 |
|
417 CallData callData; |
|
418 if (object->getCallData(callData) != CallTypeNone) { |
|
419 if (holder->inherits(&JSArray::info)) { |
|
420 builder.append("null"); |
|
421 return StringifySucceeded; |
|
422 } |
|
423 return StringifyFailedDueToUndefinedValue; |
|
424 } |
|
425 |
|
426 // Handle cycle detection, and put the holder on the stack. |
|
427 if (!m_holderCycleDetector.add(object).second) { |
|
428 throwError(m_exec, createTypeError(m_exec, "JSON.stringify cannot serialize cyclic structures.")); |
|
429 return StringifyFailed; |
|
430 } |
|
431 bool holderStackWasEmpty = m_holderStack.isEmpty(); |
|
432 m_holderStack.append(object); |
|
433 if (!holderStackWasEmpty) |
|
434 return StringifySucceeded; |
|
435 |
|
436 // If this is the outermost call, then loop to handle everything on the holder stack. |
|
437 TimeoutChecker localTimeoutChecker(m_exec->globalData().timeoutChecker); |
|
438 localTimeoutChecker.reset(); |
|
439 unsigned tickCount = localTimeoutChecker.ticksUntilNextCheck(); |
|
440 do { |
|
441 while (m_holderStack.last().appendNextProperty(*this, builder)) { |
|
442 if (m_exec->hadException()) |
|
443 return StringifyFailed; |
|
444 if (!--tickCount) { |
|
445 if (localTimeoutChecker.didTimeOut(m_exec)) { |
|
446 throwError(m_exec, createInterruptedExecutionException(&m_exec->globalData())); |
|
447 return StringifyFailed; |
|
448 } |
|
449 tickCount = localTimeoutChecker.ticksUntilNextCheck(); |
|
450 } |
|
451 } |
|
452 m_holderCycleDetector.remove(m_holderStack.last().object()); |
|
453 m_holderStack.removeLast(); |
|
454 } while (!m_holderStack.isEmpty()); |
|
455 return StringifySucceeded; |
|
456 } |
|
457 |
|
458 inline bool Stringifier::willIndent() const |
|
459 { |
|
460 return !m_gap.isEmpty(); |
|
461 } |
|
462 |
|
463 inline void Stringifier::indent() |
|
464 { |
|
465 // Use a single shared string, m_repeatedGap, so we don't keep allocating new ones as we indent and unindent. |
|
466 unsigned newSize = m_indent.size() + m_gap.size(); |
|
467 if (newSize > m_repeatedGap.size()) |
|
468 m_repeatedGap = makeString(m_repeatedGap, m_gap); |
|
469 ASSERT(newSize <= m_repeatedGap.size()); |
|
470 m_indent = m_repeatedGap.substr(0, newSize); |
|
471 } |
|
472 |
|
473 inline void Stringifier::unindent() |
|
474 { |
|
475 ASSERT(m_indent.size() >= m_gap.size()); |
|
476 m_indent = m_repeatedGap.substr(0, m_indent.size() - m_gap.size()); |
|
477 } |
|
478 |
|
479 inline void Stringifier::startNewLine(StringBuilder& builder) const |
|
480 { |
|
481 if (m_gap.isEmpty()) |
|
482 return; |
|
483 builder.append('\n'); |
|
484 builder.append(m_indent); |
|
485 } |
|
486 |
|
487 inline Stringifier::Holder::Holder(JSObject* object) |
|
488 : m_object(object) |
|
489 , m_isArray(object->inherits(&JSArray::info)) |
|
490 , m_index(0) |
|
491 { |
|
492 } |
|
493 |
|
494 bool Stringifier::Holder::appendNextProperty(Stringifier& stringifier, StringBuilder& builder) |
|
495 { |
|
496 ASSERT(m_index <= m_size); |
|
497 |
|
498 ExecState* exec = stringifier.m_exec; |
|
499 |
|
500 // First time through, initialize. |
|
501 if (!m_index) { |
|
502 if (m_isArray) { |
|
503 m_isJSArray = isJSArray(&exec->globalData(), m_object); |
|
504 m_size = m_object->get(exec, exec->globalData().propertyNames->length).toUInt32(exec); |
|
505 builder.append('['); |
|
506 } else { |
|
507 if (stringifier.m_usingArrayReplacer) |
|
508 m_propertyNames = stringifier.m_arrayReplacerPropertyNames.data(); |
|
509 else { |
|
510 PropertyNameArray objectPropertyNames(exec); |
|
511 m_object->getOwnPropertyNames(exec, objectPropertyNames); |
|
512 m_propertyNames = objectPropertyNames.releaseData(); |
|
513 } |
|
514 m_size = m_propertyNames->propertyNameVector().size(); |
|
515 builder.append('{'); |
|
516 } |
|
517 stringifier.indent(); |
|
518 } |
|
519 |
|
520 // Last time through, finish up and return false. |
|
521 if (m_index == m_size) { |
|
522 stringifier.unindent(); |
|
523 if (m_size && builder[builder.size() - 1] != '{') |
|
524 stringifier.startNewLine(builder); |
|
525 builder.append(m_isArray ? ']' : '}'); |
|
526 return false; |
|
527 } |
|
528 |
|
529 // Handle a single element of the array or object. |
|
530 unsigned index = m_index++; |
|
531 unsigned rollBackPoint = 0; |
|
532 StringifyResult stringifyResult; |
|
533 if (m_isArray) { |
|
534 // Get the value. |
|
535 JSValue value; |
|
536 if (m_isJSArray && asArray(m_object)->canGetIndex(index)) |
|
537 value = asArray(m_object)->getIndex(index); |
|
538 else { |
|
539 PropertySlot slot(m_object); |
|
540 if (!m_object->getOwnPropertySlot(exec, index, slot)) |
|
541 slot.setUndefined(); |
|
542 if (exec->hadException()) |
|
543 return false; |
|
544 value = slot.getValue(exec, index); |
|
545 } |
|
546 |
|
547 // Append the separator string. |
|
548 if (index) |
|
549 builder.append(','); |
|
550 stringifier.startNewLine(builder); |
|
551 |
|
552 // Append the stringified value. |
|
553 stringifyResult = stringifier.appendStringifiedValue(builder, value, m_object, index); |
|
554 } else { |
|
555 // Get the value. |
|
556 PropertySlot slot(m_object); |
|
557 Identifier& propertyName = m_propertyNames->propertyNameVector()[index]; |
|
558 if (!m_object->getOwnPropertySlot(exec, propertyName, slot)) |
|
559 return true; |
|
560 JSValue value = slot.getValue(exec, propertyName); |
|
561 if (exec->hadException()) |
|
562 return false; |
|
563 |
|
564 rollBackPoint = builder.size(); |
|
565 |
|
566 // Append the separator string. |
|
567 if (builder[rollBackPoint - 1] != '{') |
|
568 builder.append(','); |
|
569 stringifier.startNewLine(builder); |
|
570 |
|
571 // Append the property name. |
|
572 appendQuotedString(builder, propertyName.ustring()); |
|
573 builder.append(':'); |
|
574 if (stringifier.willIndent()) |
|
575 builder.append(' '); |
|
576 |
|
577 // Append the stringified value. |
|
578 stringifyResult = stringifier.appendStringifiedValue(builder, value, m_object, propertyName); |
|
579 } |
|
580 |
|
581 // From this point on, no access to the this pointer or to any members, because the |
|
582 // Holder object may have moved if the call to stringify pushed a new Holder onto |
|
583 // m_holderStack. |
|
584 |
|
585 switch (stringifyResult) { |
|
586 case StringifyFailed: |
|
587 builder.append("null"); |
|
588 break; |
|
589 case StringifySucceeded: |
|
590 break; |
|
591 case StringifyFailedDueToUndefinedValue: |
|
592 // This only occurs when get an undefined value for an object property. |
|
593 // In this case we don't want the separator and property name that we |
|
594 // already appended, so roll back. |
|
595 builder.resize(rollBackPoint); |
|
596 break; |
|
597 } |
|
598 |
|
599 return true; |
|
600 } |
|
601 |
|
602 // ------------------------------ JSONObject -------------------------------- |
|
603 |
|
604 const ClassInfo JSONObject::info = { "JSON", 0, 0, ExecState::jsonTable }; |
|
605 |
|
606 /* Source for JSONObject.lut.h |
|
607 @begin jsonTable |
|
608 parse JSONProtoFuncParse DontEnum|Function 1 |
|
609 stringify JSONProtoFuncStringify DontEnum|Function 1 |
|
610 @end |
|
611 */ |
|
612 |
|
613 // ECMA 15.8 |
|
614 |
|
615 bool JSONObject::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot) |
|
616 { |
|
617 return getStaticFunctionSlot<JSObject>(exec, ExecState::jsonTable(exec), this, propertyName, slot); |
|
618 } |
|
619 |
|
620 bool JSONObject::getOwnPropertyDescriptor(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor) |
|
621 { |
|
622 return getStaticFunctionDescriptor<JSObject>(exec, ExecState::jsonTable(exec), this, propertyName, descriptor); |
|
623 } |
|
624 |
|
625 void JSONObject::markStringifiers(MarkStack& markStack, Stringifier* stringifier) |
|
626 { |
|
627 stringifier->markAggregate(markStack); |
|
628 } |
|
629 |
|
630 class Walker { |
|
631 public: |
|
632 Walker(ExecState* exec, JSObject* function, CallType callType, CallData callData) |
|
633 : m_exec(exec) |
|
634 , m_function(function) |
|
635 , m_callType(callType) |
|
636 , m_callData(callData) |
|
637 { |
|
638 } |
|
639 JSValue walk(JSValue unfiltered); |
|
640 private: |
|
641 JSValue callReviver(JSObject* thisObj, JSValue property, JSValue unfiltered) |
|
642 { |
|
643 JSValue args[] = { property, unfiltered }; |
|
644 ArgList argList(args, 2); |
|
645 return call(m_exec, m_function, m_callType, m_callData, thisObj, argList); |
|
646 } |
|
647 |
|
648 friend class Holder; |
|
649 |
|
650 ExecState* m_exec; |
|
651 JSObject* m_function; |
|
652 CallType m_callType; |
|
653 CallData m_callData; |
|
654 }; |
|
655 |
|
656 // We clamp recursion well beyond anything reasonable, but we also have a timeout check |
|
657 // to guard against "infinite" execution by inserting arbitrarily large objects. |
|
658 static const unsigned maximumFilterRecursion = 40000; |
|
659 enum WalkerState { StateUnknown, ArrayStartState, ArrayStartVisitMember, ArrayEndVisitMember, |
|
660 ObjectStartState, ObjectStartVisitMember, ObjectEndVisitMember }; |
|
661 NEVER_INLINE JSValue Walker::walk(JSValue unfiltered) |
|
662 { |
|
663 Vector<PropertyNameArray, 16> propertyStack; |
|
664 Vector<uint32_t, 16> indexStack; |
|
665 Vector<JSObject*, 16> objectStack; |
|
666 Vector<JSArray*, 16> arrayStack; |
|
667 |
|
668 Vector<WalkerState, 16> stateStack; |
|
669 WalkerState state = StateUnknown; |
|
670 JSValue inValue = unfiltered; |
|
671 JSValue outValue = jsNull(); |
|
672 |
|
673 TimeoutChecker localTimeoutChecker(m_exec->globalData().timeoutChecker); |
|
674 localTimeoutChecker.reset(); |
|
675 unsigned tickCount = localTimeoutChecker.ticksUntilNextCheck(); |
|
676 while (1) { |
|
677 switch (state) { |
|
678 arrayStartState: |
|
679 case ArrayStartState: { |
|
680 ASSERT(inValue.isObject()); |
|
681 ASSERT(isJSArray(&m_exec->globalData(), asObject(inValue)) || asObject(inValue)->inherits(&JSArray::info)); |
|
682 if (objectStack.size() + arrayStack.size() > maximumFilterRecursion) |
|
683 return throwError(m_exec, createStackOverflowError(m_exec)); |
|
684 |
|
685 JSArray* array = asArray(inValue); |
|
686 arrayStack.append(array); |
|
687 indexStack.append(0); |
|
688 // fallthrough |
|
689 } |
|
690 arrayStartVisitMember: |
|
691 case ArrayStartVisitMember: { |
|
692 if (!--tickCount) { |
|
693 if (localTimeoutChecker.didTimeOut(m_exec)) |
|
694 return throwError(m_exec, createInterruptedExecutionException(&m_exec->globalData())); |
|
695 tickCount = localTimeoutChecker.ticksUntilNextCheck(); |
|
696 } |
|
697 |
|
698 JSArray* array = arrayStack.last(); |
|
699 uint32_t index = indexStack.last(); |
|
700 if (index == array->length()) { |
|
701 outValue = array; |
|
702 arrayStack.removeLast(); |
|
703 indexStack.removeLast(); |
|
704 break; |
|
705 } |
|
706 if (isJSArray(&m_exec->globalData(), array) && array->canGetIndex(index)) |
|
707 inValue = array->getIndex(index); |
|
708 else { |
|
709 PropertySlot slot; |
|
710 if (array->getOwnPropertySlot(m_exec, index, slot)) |
|
711 inValue = slot.getValue(m_exec, index); |
|
712 else |
|
713 inValue = jsUndefined(); |
|
714 } |
|
715 |
|
716 if (inValue.isObject()) { |
|
717 stateStack.append(ArrayEndVisitMember); |
|
718 goto stateUnknown; |
|
719 } else |
|
720 outValue = inValue; |
|
721 // fallthrough |
|
722 } |
|
723 case ArrayEndVisitMember: { |
|
724 JSArray* array = arrayStack.last(); |
|
725 JSValue filteredValue = callReviver(array, jsString(m_exec, UString::from(indexStack.last())), outValue); |
|
726 if (filteredValue.isUndefined()) |
|
727 array->deleteProperty(m_exec, indexStack.last()); |
|
728 else { |
|
729 if (isJSArray(&m_exec->globalData(), array) && array->canSetIndex(indexStack.last())) |
|
730 array->setIndex(indexStack.last(), filteredValue); |
|
731 else |
|
732 array->put(m_exec, indexStack.last(), filteredValue); |
|
733 } |
|
734 if (m_exec->hadException()) |
|
735 return jsNull(); |
|
736 indexStack.last()++; |
|
737 goto arrayStartVisitMember; |
|
738 } |
|
739 objectStartState: |
|
740 case ObjectStartState: { |
|
741 ASSERT(inValue.isObject()); |
|
742 ASSERT(!isJSArray(&m_exec->globalData(), asObject(inValue)) && !asObject(inValue)->inherits(&JSArray::info)); |
|
743 if (objectStack.size() + arrayStack.size() > maximumFilterRecursion) |
|
744 return throwError(m_exec, createStackOverflowError(m_exec)); |
|
745 |
|
746 JSObject* object = asObject(inValue); |
|
747 objectStack.append(object); |
|
748 indexStack.append(0); |
|
749 propertyStack.append(PropertyNameArray(m_exec)); |
|
750 object->getOwnPropertyNames(m_exec, propertyStack.last()); |
|
751 // fallthrough |
|
752 } |
|
753 objectStartVisitMember: |
|
754 case ObjectStartVisitMember: { |
|
755 if (!--tickCount) { |
|
756 if (localTimeoutChecker.didTimeOut(m_exec)) |
|
757 return throwError(m_exec, createInterruptedExecutionException(&m_exec->globalData())); |
|
758 tickCount = localTimeoutChecker.ticksUntilNextCheck(); |
|
759 } |
|
760 |
|
761 JSObject* object = objectStack.last(); |
|
762 uint32_t index = indexStack.last(); |
|
763 PropertyNameArray& properties = propertyStack.last(); |
|
764 if (index == properties.size()) { |
|
765 outValue = object; |
|
766 objectStack.removeLast(); |
|
767 indexStack.removeLast(); |
|
768 propertyStack.removeLast(); |
|
769 break; |
|
770 } |
|
771 PropertySlot slot; |
|
772 if (object->getOwnPropertySlot(m_exec, properties[index], slot)) |
|
773 inValue = slot.getValue(m_exec, properties[index]); |
|
774 else |
|
775 inValue = jsUndefined(); |
|
776 |
|
777 // The holder may be modified by the reviver function so any lookup may throw |
|
778 if (m_exec->hadException()) |
|
779 return jsNull(); |
|
780 |
|
781 if (inValue.isObject()) { |
|
782 stateStack.append(ObjectEndVisitMember); |
|
783 goto stateUnknown; |
|
784 } else |
|
785 outValue = inValue; |
|
786 // fallthrough |
|
787 } |
|
788 case ObjectEndVisitMember: { |
|
789 JSObject* object = objectStack.last(); |
|
790 Identifier prop = propertyStack.last()[indexStack.last()]; |
|
791 PutPropertySlot slot; |
|
792 JSValue filteredValue = callReviver(object, jsString(m_exec, prop.ustring()), outValue); |
|
793 if (filteredValue.isUndefined()) |
|
794 object->deleteProperty(m_exec, prop); |
|
795 else |
|
796 object->put(m_exec, prop, filteredValue, slot); |
|
797 if (m_exec->hadException()) |
|
798 return jsNull(); |
|
799 indexStack.last()++; |
|
800 goto objectStartVisitMember; |
|
801 } |
|
802 stateUnknown: |
|
803 case StateUnknown: |
|
804 if (!inValue.isObject()) { |
|
805 outValue = inValue; |
|
806 break; |
|
807 } |
|
808 JSObject* object = asObject(inValue); |
|
809 if (isJSArray(&m_exec->globalData(), object) || object->inherits(&JSArray::info)) |
|
810 goto arrayStartState; |
|
811 goto objectStartState; |
|
812 } |
|
813 if (stateStack.isEmpty()) |
|
814 break; |
|
815 |
|
816 state = stateStack.last(); |
|
817 stateStack.removeLast(); |
|
818 |
|
819 if (!--tickCount) { |
|
820 if (localTimeoutChecker.didTimeOut(m_exec)) |
|
821 return throwError(m_exec, createInterruptedExecutionException(&m_exec->globalData())); |
|
822 tickCount = localTimeoutChecker.ticksUntilNextCheck(); |
|
823 } |
|
824 } |
|
825 JSObject* finalHolder = constructEmptyObject(m_exec); |
|
826 PutPropertySlot slot; |
|
827 finalHolder->put(m_exec, m_exec->globalData().propertyNames->emptyIdentifier, outValue, slot); |
|
828 return callReviver(finalHolder, jsEmptyString(m_exec), outValue); |
|
829 } |
|
830 |
|
831 // ECMA-262 v5 15.12.2 |
|
832 EncodedJSValue JSC_HOST_CALL JSONProtoFuncParse(ExecState* exec) |
|
833 { |
|
834 if (!exec->argumentCount()) |
|
835 return throwVMError(exec, createError(exec, "JSON.parse requires at least one parameter")); |
|
836 JSValue value = exec->argument(0); |
|
837 UString source = value.toString(exec); |
|
838 if (exec->hadException()) |
|
839 return JSValue::encode(jsNull()); |
|
840 |
|
841 LiteralParser jsonParser(exec, source, LiteralParser::StrictJSON); |
|
842 JSValue unfiltered = jsonParser.tryLiteralParse(); |
|
843 if (!unfiltered) |
|
844 return throwVMError(exec, createSyntaxError(exec, "Unable to parse JSON string")); |
|
845 |
|
846 if (exec->argumentCount() < 2) |
|
847 return JSValue::encode(unfiltered); |
|
848 |
|
849 JSValue function = exec->argument(1); |
|
850 CallData callData; |
|
851 CallType callType = getCallData(function, callData); |
|
852 if (callType == CallTypeNone) |
|
853 return JSValue::encode(unfiltered); |
|
854 return JSValue::encode(Walker(exec, asObject(function), callType, callData).walk(unfiltered)); |
|
855 } |
|
856 |
|
857 // ECMA-262 v5 15.12.3 |
|
858 EncodedJSValue JSC_HOST_CALL JSONProtoFuncStringify(ExecState* exec) |
|
859 { |
|
860 if (!exec->argumentCount()) |
|
861 return throwVMError(exec, createError(exec, "No input to stringify")); |
|
862 JSValue value = exec->argument(0); |
|
863 JSValue replacer = exec->argument(1); |
|
864 JSValue space = exec->argument(2); |
|
865 return JSValue::encode(Stringifier(exec, replacer, space).stringify(value)); |
|
866 } |
|
867 |
|
868 UString JSONStringify(ExecState* exec, JSValue value, unsigned indent) |
|
869 { |
|
870 JSValue result = Stringifier(exec, jsNull(), jsNumber(exec, indent)).stringify(value); |
|
871 if (result.isUndefinedOrNull()) |
|
872 return UString(); |
|
873 return result.getString(exec); |
|
874 } |
|
875 |
|
876 } // namespace JSC |