source: webkit/trunk/JavaScriptCore/runtime/JSONObject.cpp@ 44610

Last change on this file since 44610 was 44610, checked in by [email protected], 16 years ago

Lower stringify recursion limit to deal with small windows stack.

File size: 16.7 KB
Line 
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 "ExceptionHelpers.h"
30#include "JSArray.h"
31#include "JSFunction.h"
32#include "LiteralParser.h"
33#include "Nodes.h"
34#include "PropertyNameArray.h"
35
36#include "ObjectPrototype.h"
37#include "Operations.h"
38#include <time.h>
39#include <wtf/Assertions.h>
40#include <wtf/MathExtras.h>
41
42
43namespace JSC {
44
45ASSERT_CLASS_FITS_IN_CELL(JSONObject);
46
47static JSValue JSC_HOST_CALL JSONProtoFuncStringify(ExecState*, JSObject*, JSValue, const ArgList&);
48
49}
50
51#include "JSONObject.lut.h"
52
53namespace JSC {
54
55// ------------------------------ JSONObject --------------------------------
56
57const ClassInfo JSONObject::info = { "JSON", 0, 0, ExecState::jsonTable };
58
59/* Source for JSONObject.lut.h
60@begin jsonTable
61 stringify JSONProtoFuncStringify DontEnum|Function 1
62@end
63*/
64
65// ECMA 15.8
66
67bool JSONObject::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot)
68{
69 const HashEntry* entry = ExecState::jsonTable(exec)->entry(exec, propertyName);
70
71 if (!entry)
72 return JSObject::getOwnPropertySlot(exec, propertyName, slot);
73
74 ASSERT(entry->attributes() & Function);
75 setUpStaticFunctionSlot(exec, entry, this, propertyName, slot);
76 return true;
77}
78
79class Stringifier {
80 typedef UString StringBuilder;
81 // <https://bugs.webkit.org/show_bug.cgi?id=26276> arbitrary limits for recursion
82 // are bad
83 enum { MaxObjectDepth = 512 };
84public:
85 Stringifier(ExecState* exec, JSValue replacer, const UString& gap)
86 : m_exec(exec)
87 , m_replacer(replacer)
88 , m_gap(gap)
89 , m_arrayReplacerPropertyNames(exec)
90 , m_usingArrayReplacer(false)
91 , m_replacerCallType(CallTypeNone)
92 {
93 if (m_replacer.isObject()) {
94 if (asObject(m_replacer)->inherits(&JSArray::info)) {
95 JSObject* array = asObject(m_replacer);
96 bool isRealJSArray = isJSArray(&m_exec->globalData(), m_replacer);
97 unsigned length = array->get(m_exec, m_exec->globalData().propertyNames->length).toUInt32(m_exec);
98 for (unsigned i = 0; i < length; i++) {
99 JSValue name;
100 if (isRealJSArray && asArray(array)->canGetIndex(i))
101 name = asArray(array)->getIndex(i);
102 else {
103 name = array->get(m_exec, i);
104 if (exec->hadException())
105 break;
106 }
107 if (!name.isString())
108 continue;
109
110 UString propertyName = name.toString(m_exec);
111 if (exec->hadException())
112 return;
113 m_arrayReplacerPropertyNames.add(Identifier(m_exec, propertyName));
114 }
115 m_usingArrayReplacer = true;
116 } else
117 m_replacerCallType = asObject(m_replacer)->getCallData(m_replacerCallData);
118 }
119 }
120
121 JSValue stringify(JSValue value)
122 {
123 JSObject* obj = constructEmptyObject(m_exec);
124 if (m_exec->hadException())
125 return jsNull();
126
127 Identifier emptyIdent(m_exec, "");
128 obj->putDirect(emptyIdent, value);
129 StringKeyGenerator key(emptyIdent);
130 value = toJSONValue(key, value);
131 if (m_exec->hadException())
132 return jsNull();
133
134 StringBuilder builder;
135 if (!stringify(builder, obj, key, value))
136 return jsUndefined();
137
138 return (m_exec->hadException())? m_exec->exception() : jsString(m_exec, builder);
139 }
140
141private:
142 void appendString(StringBuilder& builder, const UString& value)
143 {
144 int length = value.size();
145 const UChar* data = value.data();
146 builder.reserveCapacity(builder.size() + length + 2 + 8); // +2 for "'s +8 for a buffer
147 builder.append('\"');
148 int index = 0;
149 while (index < length) {
150 int start = index;
151 while (index < length && (data[index] > 0x1f && data[index] != '"' && data[index] != '\\'))
152 index++;
153 builder.append(data + start, index - start);
154 if (index < length) {
155 switch(data[index]){
156 case '\t': {
157 UChar tab[] = {'\\','t'};
158 builder.append(tab, 2);
159 break;
160 }
161 case '\r': {
162 UChar cr[] = {'\\','r'};
163 builder.append(cr, 2);
164 break;
165 }
166 case '\n': {
167 UChar nl[] = {'\\','n'};
168 builder.append(nl, 2);
169 break;
170 }
171 case '\f': {
172 UChar tab[] = {'\\','f'};
173 builder.append(tab, 2);
174 break;
175 }
176 case '\b': {
177 UChar bs[] = {'\\','b'};
178 builder.append(bs, 2);
179 break;
180 }
181 case '\"': {
182 UChar quote[] = {'\\','"'};
183 builder.append(quote, 2);
184 break;
185 }
186 case '\\': {
187 UChar slash[] = {'\\', '\\'};
188 builder.append(slash, 2);
189 break;
190 }
191 default:
192 int ch = (int)data[index];
193 static const char* hexStr = "0123456789abcdef";
194 UChar oct[] = {'\\', 'u', hexStr[(ch >> 12) & 15], hexStr[(ch >> 8) & 15], hexStr[(ch >> 4) & 15], hexStr[(ch >> 0) & 15]};
195 builder.append(oct, sizeof(oct) / sizeof(UChar));
196 break;
197 }
198 index++;
199 }
200 }
201 builder.append('\"');
202 }
203
204 class StringKeyGenerator {
205 public:
206 StringKeyGenerator(const Identifier& property)
207 : m_property(property)
208 {
209 }
210 JSValue getKey(ExecState* exec) { if (!m_key) m_key = jsString(exec, m_property.ustring()); return m_key; }
211
212 private:
213 Identifier m_property;
214 JSValue m_key;
215 };
216
217 class IntKeyGenerator {
218 public:
219 IntKeyGenerator(uint32_t property)
220 : m_property(property)
221 {
222 }
223 JSValue getKey(ExecState* exec) { if (!m_key) m_key = jsNumber(exec, m_property); return m_key; }
224
225 private:
226 uint32_t m_property;
227 JSValue m_key;
228 };
229
230 template <typename KeyGenerator> JSValue toJSONValue(KeyGenerator& jsKey, JSValue jsValue)
231 {
232 ASSERT(!m_exec->hadException());
233 if (!jsValue.isObject() || !asObject(jsValue)->hasProperty(m_exec, m_exec->globalData().propertyNames->toJSON))
234 return jsValue;
235
236 JSValue jsToJSON = asObject(jsValue)->get(m_exec, m_exec->globalData().propertyNames->toJSON);
237
238 if (!jsToJSON.isObject())
239 return jsValue;
240
241 if (m_exec->hadException())
242 return jsNull();
243
244 JSObject* object = asObject(jsToJSON);
245 CallData callData;
246 CallType callType = object->getCallData(callData);
247
248 if (callType == CallTypeNone)
249 return jsValue;
250
251 JSValue list[] = { jsKey.getKey(m_exec) };
252 ArgList args(list, sizeof(list) / sizeof(JSValue));
253 return call(m_exec, object, callType, callData, jsValue, args);
254 }
255
256 bool stringifyArray(StringBuilder& builder, JSArray* array)
257 {
258 if (m_objectStack.contains(array)) {
259 throwError(m_exec, TypeError);
260 return false;
261 }
262
263 if (m_objectStack.size() > MaxObjectDepth) {
264 m_exec->setException(createStackOverflowError(m_exec));
265 return false;
266 }
267
268 m_objectStack.add(array);
269 UString stepBack = m_indent;
270 m_indent.append(m_gap);
271 Vector<StringBuilder, 16> partial;
272 unsigned len = array->get(m_exec, m_exec->globalData().propertyNames->length).toUInt32(m_exec);
273 bool isRealJSArray = isJSArray(&m_exec->globalData(), array);
274 for (unsigned i = 0; i < len; i++) {
275 JSValue value;
276 if (isRealJSArray && array->canGetIndex(i))
277 value = array->getIndex(i);
278 else {
279 value = array->get(m_exec, i);
280 if (m_exec->hadException())
281 return false;
282 }
283 StringBuilder result;
284 IntKeyGenerator key(i);
285 value = toJSONValue(key, value);
286 if (partial.size() && m_gap.isEmpty())
287 partial.last().append(',');
288 if (!stringify(result, array, key, value) && !m_exec->hadException())
289 result.append("null");
290 else if (m_exec->hadException())
291 return false;
292 partial.append(result);
293 }
294
295 if (partial.isEmpty())
296 builder.append("[]");
297 else {
298 if (m_gap.isEmpty()) {
299 builder.append('[');
300 for (unsigned i = 0; i < partial.size(); i++)
301 builder.append(partial[i]);
302 builder.append(']');
303 } else {
304 UString separator(",\n");
305 separator.append(m_indent);
306 builder.append("[\n");
307 builder.append(m_indent);
308 for (unsigned i = 0; i < partial.size(); i++) {
309 builder.append(partial[i]);
310 if (i < partial.size() - 1)
311 builder.append(separator);
312 }
313 builder.append('\n');
314 builder.append(stepBack);
315 builder.append(']');
316 }
317 }
318
319 m_indent = stepBack;
320 m_objectStack.remove(array);
321 return true;
322 }
323
324 bool stringifyObject(StringBuilder& builder, JSObject* object)
325 {
326 if (m_objectStack.contains(object)) {
327 throwError(m_exec, TypeError);
328 return false;
329 }
330
331 if (m_objectStack.size() > MaxObjectDepth) {
332 m_exec->setException(createStackOverflowError(m_exec));
333 return false;
334 }
335
336 m_objectStack.add(object);
337 UString stepBack = m_indent;
338 m_indent.append(m_gap);
339 Vector<StringBuilder, 16> partial;
340
341 PropertyNameArray objectPropertyNames(m_exec);
342 PropertyNameArray& sourcePropertyNames(m_arrayReplacerPropertyNames);
343 if (!m_usingArrayReplacer) {
344 object->getPropertyNames(m_exec, objectPropertyNames);
345 sourcePropertyNames = objectPropertyNames;
346 }
347
348 PropertyNameArray::const_iterator propEnd = sourcePropertyNames.end();
349 for (PropertyNameArray::const_iterator propIter = sourcePropertyNames.begin(); propIter != propEnd; propIter++) {
350 if (!object->hasOwnProperty(m_exec, *propIter))
351 continue;
352
353 JSValue value = object->get(m_exec, *propIter);
354 if (m_exec->hadException())
355 return false;
356
357 if (value.isUndefined())
358 continue;
359
360 StringBuilder result;
361 appendString(result,propIter->ustring());
362 result.append(':');
363 if (!m_gap.isEmpty())
364 result.append(' ');
365 StringKeyGenerator key(*propIter);
366 value = toJSONValue(key, value);
367 if (m_exec->hadException())
368 return false;
369
370 if (value.isUndefined())
371 continue;
372
373 stringify(result, object, key, value);
374 partial.append(result);
375 }
376
377 if (partial.isEmpty())
378 builder.append("{}");
379 else {
380 if (m_gap.isEmpty()) {
381 builder.append('{');
382 for (unsigned i = 0; i < partial.size(); i++) {
383 if (i > 0)
384 builder.append(',');
385 builder.append(partial[i]);
386 }
387 builder.append('}');
388 } else {
389 UString separator(",\n");
390 separator.append(m_indent);
391 builder.append("{\n");
392 builder.append(m_indent);
393 for (unsigned i = 0; i < partial.size(); i++) {
394 builder.append(partial[i]);
395 if (i < partial.size() - 1)
396 builder.append(separator);
397 }
398 builder.append('\n');
399 builder.append(stepBack);
400 builder.append('}');
401 }
402
403 }
404
405 m_indent = stepBack;
406 m_objectStack.remove(object);
407 return true;
408 }
409
410 template <typename KeyGenerator> bool stringify(StringBuilder& builder, JSValue holder, KeyGenerator key, JSValue value)
411 {
412 if (m_replacerCallType != CallTypeNone) {
413 JSValue list[] = {key.getKey(m_exec), value};
414 ArgList args(list, sizeof(list) / sizeof(JSValue));
415 value = call(m_exec, m_replacer, m_replacerCallType, m_replacerCallData, holder, args);
416 if (m_exec->hadException())
417 return false;
418 }
419
420 if (value.isNull()) {
421 builder.append("null");
422 return true;
423 }
424
425 if (value.isBoolean()) {
426 builder.append(value.getBoolean() ? "true" : "false");
427 return true;
428 }
429
430 if (value.isObject()) {
431 if (asObject(value)->inherits(&NumberObject::info) || asObject(value)->inherits(&StringObject::info))
432 value = static_cast<JSWrapperObject*>(asObject(value))->internalValue();
433 }
434
435 if (value.isString()) {
436 appendString(builder, value.toString(m_exec));
437 return true;
438 }
439
440 if (value.isNumber()) {
441 double v = value.toNumber(m_exec);
442 if (!isfinite(v))
443 builder.append("null");
444 else
445 builder.append(UString::from(v));
446 return true;
447 }
448
449 if (value.isObject()) {
450 if (asObject(value)->inherits(&JSArray::info))
451 return stringifyArray(builder, asArray(value));
452 return stringifyObject(builder, asObject(value));
453 }
454
455 return false;
456 }
457 ExecState* m_exec;
458 JSValue m_replacer;
459 UString m_gap;
460 UString m_indent;
461 HashSet<JSObject*> m_objectStack;
462 PropertyNameArray m_arrayReplacerPropertyNames;
463 bool m_usingArrayReplacer;
464 CallType m_replacerCallType;
465 CallData m_replacerCallData;
466};
467
468// ECMA-262 v5 15.12.3
469JSValue JSC_HOST_CALL JSONProtoFuncStringify(ExecState* exec, JSObject*, JSValue, const ArgList& args)
470{
471 if (args.isEmpty())
472 return throwError(exec, GeneralError, "No input to stringify");
473
474 JSValue value = args.at(0);
475 JSValue replacer = args.at(1);
476 JSValue space = args.at(2);
477
478 UString gap;
479 if (space.isObject()) {
480 if (asObject(space)->inherits(&NumberObject::info) || asObject(space)->inherits(&StringObject::info))
481 space = static_cast<JSWrapperObject*>(asObject(space))->internalValue();
482 }
483 if (space.isNumber()) {
484 double v = space.toNumber(exec);
485 if (v > 100)
486 v = 100;
487 if (!(v > 0))
488 v = 0;
489 for (int i = 0; i < v; i++)
490 gap.append(' ');
491 } else if (space.isString())
492 gap = space.toString(exec);
493
494 Stringifier stringifier(exec, args.at(1), gap);
495 return stringifier.stringify(value);
496}
497
498} // namespace JSC
Note: See TracBrowser for help on using the repository browser.