Changeset 44813 in webkit
- Timestamp:
- Jun 18, 2009 12:18:12 PM (15 years ago)
- Location:
- trunk
- Files:
-
- 12 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/JavaScriptCore/ChangeLog
r44797 r44813 1 2009-06-17 Darin Adler <darin@apple.com> 2 3 Reviewed by Oliver Hunt. 4 5 Bug 26429: Make JSON.stringify non-recursive so it can handle objects 6 of arbitrary complexity 7 https://bugs.webkit.org/show_bug.cgi?id=26429 8 9 For marking I decided not to use gcProtect, because this is inside the engine 10 so it's easy enough to just do marking. And that darned gcProtect does locking! 11 Oliver tried to convince me to used MarkedArgumentBuffer, but the constructor 12 for that class says "FIXME: Remove all clients of this API, then remove this API." 13 14 * runtime/Collector.cpp: 15 (JSC::Heap::collect): Add a call to JSONObject::markStringifiers. 16 17 * runtime/CommonIdentifiers.cpp: 18 (JSC::CommonIdentifiers::CommonIdentifiers): Added emptyIdentifier. 19 * runtime/CommonIdentifiers.h: Ditto. 20 21 * runtime/JSGlobalData.cpp: 22 (JSC::JSGlobalData::JSGlobalData): Initialize firstStringifierToMark to 0. 23 * runtime/JSGlobalData.h: Added firstStringifierToMark. 24 25 * runtime/JSONObject.cpp: Cut down the includes to the needed ones only. 26 (JSC::unwrapNumberOrString): Added. Helper for unwrapping number and string 27 objects to get their number and string values. 28 (JSC::ReplacerPropertyName::ReplacerPropertyName): Added. The class is used 29 to wrap an identifier or integer so we don't have to do any work unless we 30 actually call a replacer. 31 (JSC::ReplacerPropertyName::value): Added. 32 (JSC::gap): Added. Helper function for the Stringifier constructor. 33 (JSC::PropertyNameForFunctionCall::PropertyNameForFunctionCall): Added. 34 The class is used to wrap an identifier or integer so we don't have to 35 allocate a number or string until we actually call toJSON or a replacer. 36 (JSC::PropertyNameForFunctionCall::asJSValue): Added. 37 (JSC::Stringifier::Stringifier): Updated and moved out of the class 38 definition. Added code to hook this into a singly linked list for marking. 39 (JSC::Stringifier::~Stringifier): Remove from the singly linked list. 40 (JSC::Stringifier::mark): Mark all the objects in the holder stacks. 41 (JSC::Stringifier::stringify): Updated. 42 (JSC::Stringifier::appendQuotedString): Tweaked and streamlined a bit. 43 (JSC::Stringifier::toJSON): Renamed from toJSONValue. 44 (JSC::Stringifier::appendStringifiedValue): Renamed from stringify. 45 Added code to use the m_holderStack to do non-recursive stringify of 46 objects and arrays. This code also uses the timeout checker since in 47 pathological cases it could be slow even without calling into the 48 JavaScript virtual machine. 49 (JSC::Stringifier::willIndent): Added. 50 (JSC::Stringifier::indent): Added. 51 (JSC::Stringifier::unindent): Added. 52 (JSC::Stringifier::startNewLine): Added. 53 (JSC::Stringifier::Holder::Holder): Added. 54 (JSC::Stringifier::Holder::appendNextProperty): Added. This is the 55 function that handles the format of arrays and objects. 56 (JSC::JSONObject::getOwnPropertySlot): Moved this down to the bottom 57 of the file so the JSONObject class is not interleaved with the 58 Stringifier class. 59 (JSC::JSONObject::markStringifiers): Added. Calls mark. 60 (JSC::JSONProtoFuncStringify): Streamlined the code here. The code 61 to compute the gap string is now a separate function. 62 63 * runtime/JSONObject.h: Made everything private. Added markStringifiers. 64 1 65 2009-06-17 Oliver Hunt <oliver@apple.com> 2 66 -
trunk/JavaScriptCore/runtime/Collector.cpp
r44224 r44813 28 28 #include "JSGlobalObject.h" 29 29 #include "JSLock.h" 30 #include "JSONObject.h" 30 31 #include "JSString.h" 31 32 #include "JSValue.h" … … 992 993 if (m_globalData->scopeNodeBeingReparsed) 993 994 m_globalData->scopeNodeBeingReparsed->mark(); 995 if (m_globalData->firstStringifierToMark) 996 JSONObject::markStringifiers(m_globalData->firstStringifierToMark); 994 997 995 998 JAVASCRIPTCORE_GC_MARKED(); -
trunk/JavaScriptCore/runtime/CommonIdentifiers.cpp
r44224 r44813 1 1 /* 2 * Copyright (C) 2003, 2007 Apple Inc. All rights reserved.2 * Copyright (C) 2003, 2007, 2009 Apple Inc. All rights reserved. 3 3 * 4 4 * This library is free software; you can redistribute it and/or … … 24 24 namespace JSC { 25 25 26 const char* const nullCString = 0;26 static const char* const nullCString = 0; 27 27 28 28 #define INITIALIZE_PROPERTY_NAME(name) , name(globalData, #name) … … 30 30 CommonIdentifiers::CommonIdentifiers(JSGlobalData* globalData) 31 31 : nullIdentifier(globalData, nullCString) 32 , emptyIdentifier(globalData, "") 32 33 , underscoreProto(globalData, "__proto__") 33 34 , thisIdentifier(globalData, "this") -
trunk/JavaScriptCore/runtime/CommonIdentifiers.h
r44550 r44813 1 1 /* 2 * Copyright (C) 2003, 2007 Apple Computer, Inc2 * Copyright (C) 2003, 2007, 2009 Apple Inc. All rights reserved. 3 3 * 4 4 * This library is free software; you can redistribute it and/or … … 77 77 public: 78 78 const Identifier nullIdentifier; 79 const Identifier emptyIdentifier; 79 80 const Identifier underscoreProto; 80 81 const Identifier thisIdentifier; -
trunk/JavaScriptCore/runtime/JSGlobalData.cpp
r44550 r44813 140 140 , dynamicGlobalObject(0) 141 141 , scopeNodeBeingReparsed(0) 142 , firstStringifierToMark(0) 142 143 { 143 144 #if PLATFORM(MAC) -
trunk/JavaScriptCore/runtime/JSGlobalData.h
r44705 r44813 55 55 class Parser; 56 56 class ScopeNode; 57 class Stringifier; 57 58 class Structure; 58 59 class UString; 60 59 61 struct HashTable; 60 62 struct VPtrSet; … … 147 149 148 150 ScopeNode* scopeNodeBeingReparsed; 151 Stringifier* firstStringifierToMark; 149 152 150 153 private: -
trunk/JavaScriptCore/runtime/JSONObject.cpp
r44610 r44813 27 27 #include "JSONObject.h" 28 28 29 #include "Error.h" 29 30 #include "ExceptionHelpers.h" 30 31 #include "JSArray.h" 31 #include "JSFunction.h"32 #include "LiteralParser.h"33 #include "Nodes.h"34 32 #include "PropertyNameArray.h" 35 36 #include "ObjectPrototype.h"37 #include "Operations.h"38 #include <time.h>39 #include <wtf/Assertions.h>40 33 #include <wtf/MathExtras.h> 41 34 42 43 35 namespace JSC { 44 36 … … 52 44 53 45 namespace JSC { 46 47 // PropertyNameForFunctionCall objects must be on the stack, since the JSValue that they create is not marked. 48 class PropertyNameForFunctionCall { 49 public: 50 PropertyNameForFunctionCall(const Identifier&); 51 PropertyNameForFunctionCall(unsigned); 52 53 JSValue value(ExecState*) const; 54 55 private: 56 const Identifier* m_identifier; 57 unsigned m_number; 58 mutable JSValue m_value; 59 }; 60 61 class Stringifier : Noncopyable { 62 public: 63 Stringifier(ExecState*, JSValue replacer, JSValue space); 64 ~Stringifier(); 65 JSValue stringify(JSValue); 66 67 void mark(); 68 69 private: 70 typedef UString StringBuilder; 71 72 class Holder { 73 public: 74 Holder(JSObject*); 75 76 JSObject* object() const { return m_object; } 77 78 bool appendNextProperty(Stringifier&, StringBuilder&); 79 80 private: 81 JSObject* const m_object; 82 const bool m_isArray; 83 bool m_isJSArray; 84 unsigned m_index; 85 unsigned m_size; 86 RefPtr<PropertyNameArrayData> m_propertyNames; 87 }; 88 89 friend class Holder; 90 91 static void appendQuotedString(StringBuilder&, const UString&); 92 93 JSValue toJSON(JSValue, const PropertyNameForFunctionCall&); 94 95 enum StringifyResult { StringifyFailed, StringifySucceeded, StringifyFailedDueToUndefinedValue }; 96 StringifyResult appendStringifiedValue(StringBuilder&, JSValue, JSObject* holder, const PropertyNameForFunctionCall&); 97 98 bool willIndent() const; 99 void indent(); 100 void unindent(); 101 void startNewLine(StringBuilder&) const; 102 103 Stringifier* const m_nextStringifierToMark; 104 ExecState* const m_exec; 105 const JSValue m_replacer; 106 bool m_usingArrayReplacer; 107 PropertyNameArray m_arrayReplacerPropertyNames; 108 CallType m_replacerCallType; 109 CallData m_replacerCallData; 110 const UString m_gap; 111 112 HashSet<JSObject*> m_holderCycleDetector; 113 Vector<Holder, 16> m_holderStack; 114 UString m_repeatedGap; 115 UString m_indent; 116 }; 117 118 // ------------------------------ helper functions -------------------------------- 119 120 static inline JSValue unwrapNumberOrString(JSValue value) 121 { 122 if (!value.isObject()) 123 return value; 124 if (!asObject(value)->inherits(&NumberObject::info) && !asObject(value)->inherits(&StringObject::info)) 125 return value; 126 return static_cast<JSWrapperObject*>(asObject(value))->internalValue(); 127 } 128 129 static inline UString gap(JSValue space) 130 { 131 space = unwrapNumberOrString(space); 132 133 // If the space value is a number, create a gap string with that number of spaces. 134 double spaceCount; 135 if (space.getNumber(spaceCount)) { 136 const int maxSpaceCount = 100; 137 int count; 138 if (spaceCount > maxSpaceCount) 139 count = maxSpaceCount; 140 else if (!(spaceCount > 0)) 141 count = 0; 142 else 143 count = static_cast<int>(spaceCount); 144 UChar spaces[maxSpaceCount]; 145 for (int i = 0; i < count; ++i) 146 spaces[i] = ' '; 147 return UString(spaces, count); 148 } 149 150 // If the space value is a string, use it as the gap string, otherwise use no gap string. 151 return space.getString(); 152 } 153 154 // ------------------------------ PropertyNameForFunctionCall -------------------------------- 155 156 inline PropertyNameForFunctionCall::PropertyNameForFunctionCall(const Identifier& identifier) 157 : m_identifier(&identifier) 158 { 159 } 160 161 inline PropertyNameForFunctionCall::PropertyNameForFunctionCall(unsigned number) 162 : m_identifier(0) 163 , m_number(number) 164 { 165 } 166 167 JSValue PropertyNameForFunctionCall::value(ExecState* exec) const 168 { 169 if (!m_value) { 170 if (m_identifier) 171 m_value = jsString(exec, m_identifier->ustring()); 172 else 173 m_value = jsNumber(exec, m_number); 174 } 175 return m_value; 176 } 177 178 // ------------------------------ Stringifier -------------------------------- 179 180 Stringifier::Stringifier(ExecState* exec, JSValue replacer, JSValue space) 181 : m_nextStringifierToMark(exec->globalData().firstStringifierToMark) 182 , m_exec(exec) 183 , m_replacer(replacer) 184 , m_usingArrayReplacer(false) 185 , m_arrayReplacerPropertyNames(exec) 186 , m_replacerCallType(CallTypeNone) 187 , m_gap(gap(space)) 188 { 189 exec->globalData().firstStringifierToMark = this; 190 191 if (!m_replacer.isObject()) 192 return; 193 194 if (asObject(m_replacer)->inherits(&JSArray::info)) { 195 m_usingArrayReplacer = true; 196 JSObject* array = asObject(m_replacer); 197 unsigned length = array->get(exec, exec->globalData().propertyNames->length).toUInt32(exec); 198 for (unsigned i = 0; i < length; ++i) { 199 JSValue name = array->get(exec, i); 200 if (exec->hadException()) 201 break; 202 UString propertyName; 203 if (!name.getString(propertyName)) 204 continue; 205 if (exec->hadException()) 206 return; 207 m_arrayReplacerPropertyNames.add(Identifier(exec, propertyName)); 208 } 209 return; 210 } 211 212 m_replacerCallType = asObject(m_replacer)->getCallData(m_replacerCallData); 213 } 214 215 Stringifier::~Stringifier() 216 { 217 ASSERT(m_exec->globalData().firstStringifierToMark == this); 218 m_exec->globalData().firstStringifierToMark = m_nextStringifierToMark; 219 } 220 221 void Stringifier::mark() 222 { 223 for (Stringifier* stringifier = this; stringifier; stringifier = stringifier->m_nextStringifierToMark) { 224 size_t size = m_holderStack.size(); 225 for (size_t i = 0; i < size; ++i) { 226 JSObject* object = m_holderStack[i].object(); 227 if (!object->marked()) 228 object->mark(); 229 } 230 } 231 } 232 233 JSValue Stringifier::stringify(JSValue value) 234 { 235 JSObject* object = constructEmptyObject(m_exec); 236 if (m_exec->hadException()) 237 return jsNull(); 238 239 PropertyNameForFunctionCall emptyPropertyName(m_exec->globalData().propertyNames->emptyIdentifier); 240 object->putDirect(m_exec->globalData().propertyNames->emptyIdentifier, value); 241 242 StringBuilder result; 243 if (appendStringifiedValue(result, value, object, emptyPropertyName) != StringifySucceeded) 244 return jsUndefined(); 245 if (m_exec->hadException()) 246 return jsNull(); 247 248 return jsString(m_exec, result); 249 } 250 251 void Stringifier::appendQuotedString(StringBuilder& builder, const UString& value) 252 { 253 int length = value.size(); 254 255 // String length plus 2 for quote marks plus 8 so we can accomodate a few escaped characters. 256 builder.reserveCapacity(builder.size() + length + 2 + 8); 257 258 builder.append('"'); 259 260 const UChar* data = value.data(); 261 for (int i = 0; i < length; ++i) { 262 int start = i; 263 while (i < length && (data[i] > 0x1F && data[i] != '"' && data[i] != '\\')) 264 ++i; 265 builder.append(data + start, i - start); 266 if (i >= length) 267 break; 268 switch (data[i]) { 269 case '\t': 270 builder.append('\\'); 271 builder.append('t'); 272 break; 273 case '\r': 274 builder.append('\\'); 275 builder.append('r'); 276 break; 277 case '\n': 278 builder.append('\\'); 279 builder.append('n'); 280 break; 281 case '\f': 282 builder.append('\\'); 283 builder.append('f'); 284 break; 285 case '\b': 286 builder.append('\\'); 287 builder.append('b'); 288 break; 289 case '"': 290 builder.append('\\'); 291 builder.append('"'); 292 break; 293 case '\\': 294 builder.append('\\'); 295 builder.append('\\'); 296 break; 297 default: 298 static const char hexDigits[] = "0123456789abcdef"; 299 UChar ch = data[i]; 300 UChar hex[] = { '\\', 'u', hexDigits[(ch >> 12) & 0xF], hexDigits[(ch >> 8) & 0xF], hexDigits[(ch >> 4) & 0xF], hexDigits[ch & 0xF] }; 301 builder.append(hex, sizeof(hex) / sizeof(UChar)); 302 break; 303 } 304 } 305 306 builder.append('"'); 307 } 308 309 inline JSValue Stringifier::toJSON(JSValue value, const PropertyNameForFunctionCall& propertyName) 310 { 311 ASSERT(!m_exec->hadException()); 312 if (!value.isObject() || !asObject(value)->hasProperty(m_exec, m_exec->globalData().propertyNames->toJSON)) 313 return value; 314 315 JSValue toJSONFunction = asObject(value)->get(m_exec, m_exec->globalData().propertyNames->toJSON); 316 if (m_exec->hadException()) 317 return jsNull(); 318 319 if (!toJSONFunction.isObject()) 320 return value; 321 322 JSObject* object = asObject(toJSONFunction); 323 CallData callData; 324 CallType callType = object->getCallData(callData); 325 if (callType == CallTypeNone) 326 return value; 327 328 JSValue list[] = { propertyName.value(m_exec) }; 329 ArgList args(list, sizeof(list) / sizeof(JSValue)); 330 return call(m_exec, object, callType, callData, value, args); 331 } 332 333 Stringifier::StringifyResult Stringifier::appendStringifiedValue(StringBuilder& builder, JSValue value, JSObject* holder, const PropertyNameForFunctionCall& propertyName) 334 { 335 // Call the toJSON function. 336 value = toJSON(value, propertyName); 337 if (m_exec->hadException()) 338 return StringifyFailed; 339 if (value.isUndefined() && !holder->inherits(&JSArray::info)) 340 return StringifyFailedDueToUndefinedValue; 341 342 // Call the replacer function. 343 if (m_replacerCallType != CallTypeNone) { 344 JSValue list[] = { propertyName.value(m_exec), value }; 345 ArgList args(list, sizeof(list) / sizeof(JSValue)); 346 value = call(m_exec, m_replacer, m_replacerCallType, m_replacerCallData, holder, args); 347 if (m_exec->hadException()) 348 return StringifyFailed; 349 } 350 351 if (value.isNull()) { 352 builder.append("null"); 353 return StringifySucceeded; 354 } 355 356 if (value.isBoolean()) { 357 builder.append(value.getBoolean() ? "true" : "false"); 358 return StringifySucceeded; 359 } 360 361 value = unwrapNumberOrString(value); 362 363 UString stringValue; 364 if (value.getString(stringValue)) { 365 appendQuotedString(builder, stringValue); 366 return StringifySucceeded; 367 } 368 369 double numericValue; 370 if (value.getNumber(numericValue)) { 371 if (!isfinite(numericValue)) 372 builder.append("null"); 373 else 374 builder.append(UString::from(numericValue)); 375 return StringifySucceeded; 376 } 377 378 if (!value.isObject()) 379 return StringifyFailed; 380 381 JSObject* object = asObject(value); 382 383 // Handle cycle detection, and put the holder on the stack. 384 if (!m_holderCycleDetector.add(object).second) { 385 throwError(m_exec, TypeError); 386 return StringifyFailed; 387 } 388 bool holderStackWasEmpty = m_holderStack.isEmpty(); 389 m_holderStack.append(object); 390 if (!holderStackWasEmpty) 391 return StringifySucceeded; 392 393 // If this is the outermost call, then loop to handle everything on the holder stack. 394 TimeoutChecker localTimeoutChecker(m_exec->globalData().timeoutChecker); 395 localTimeoutChecker.reset(); 396 unsigned tickCount = localTimeoutChecker.ticksUntilNextCheck(); 397 do { 398 while (m_holderStack.last().appendNextProperty(*this, builder)) { 399 if (m_exec->hadException()) 400 return StringifyFailed; 401 if (!--tickCount) { 402 if (localTimeoutChecker.didTimeOut(m_exec)) { 403 m_exec->setException(createInterruptedExecutionException(&m_exec->globalData())); 404 return StringifyFailed; 405 } 406 tickCount = localTimeoutChecker.ticksUntilNextCheck(); 407 } 408 } 409 m_holderCycleDetector.remove(m_holderStack.last().object()); 410 m_holderStack.removeLast(); 411 } while (!m_holderStack.isEmpty()); 412 return StringifySucceeded; 413 } 414 415 inline bool Stringifier::willIndent() const 416 { 417 return !m_gap.isEmpty(); 418 } 419 420 inline void Stringifier::indent() 421 { 422 // Use a single shared string, m_repeatedGap, so we don't keep allocating new ones as we indent and unindent. 423 int newSize = m_indent.size() + m_gap.size(); 424 if (newSize > m_repeatedGap.size()) 425 m_repeatedGap.append(m_gap); 426 ASSERT(newSize <= m_repeatedGap.size()); 427 m_indent = m_repeatedGap.substr(0, newSize); 428 } 429 430 inline void Stringifier::unindent() 431 { 432 ASSERT(m_indent.size() >= m_gap.size()); 433 m_indent = m_repeatedGap.substr(0, m_indent.size() - m_gap.size()); 434 } 435 436 inline void Stringifier::startNewLine(StringBuilder& builder) const 437 { 438 if (m_gap.isEmpty()) 439 return; 440 builder.append('\n'); 441 builder.append(m_indent); 442 } 443 444 inline Stringifier::Holder::Holder(JSObject* object) 445 : m_object(object) 446 , m_isArray(object->inherits(&JSArray::info)) 447 , m_index(0) 448 { 449 } 450 451 bool Stringifier::Holder::appendNextProperty(Stringifier& stringifier, StringBuilder& builder) 452 { 453 ASSERT(m_index <= m_size); 454 455 ExecState* exec = stringifier.m_exec; 456 457 // First time through, initialize. 458 if (!m_index) { 459 if (m_isArray) { 460 m_isJSArray = isJSArray(&exec->globalData(), m_object); 461 m_size = m_object->get(exec, exec->globalData().propertyNames->length).toUInt32(exec); 462 builder.append('['); 463 } else { 464 if (stringifier.m_usingArrayReplacer) 465 m_propertyNames = stringifier.m_arrayReplacerPropertyNames.data(); 466 else { 467 PropertyNameArray objectPropertyNames(exec); 468 m_object->getPropertyNames(exec, objectPropertyNames); 469 m_propertyNames = objectPropertyNames.releaseData(); 470 } 471 m_size = m_propertyNames->propertyNameVector().size(); 472 builder.append('{'); 473 } 474 stringifier.indent(); 475 } 476 477 // Last time through, finish up and return false. 478 if (m_index == m_size) { 479 stringifier.unindent(); 480 if (m_size && builder[builder.size() - 1] != '{') 481 stringifier.startNewLine(builder); 482 builder.append(m_isArray ? ']' : '}'); 483 return false; 484 } 485 486 // Handle a single element of the array or object. 487 unsigned index = m_index++; 488 unsigned rollBackPoint = 0; 489 StringifyResult stringifyResult; 490 if (m_isArray) { 491 // Get the value. 492 JSValue value; 493 if (m_isJSArray && asArray(m_object)->canGetIndex(index)) 494 value = asArray(m_object)->getIndex(index); 495 else { 496 PropertySlot slot(m_object); 497 if (!m_object->getOwnPropertySlot(exec, index, slot)) 498 slot.setUndefined(); 499 if (exec->hadException()) 500 return false; 501 value = slot.getValue(exec, index); 502 } 503 504 // Append the separator string. 505 if (index) 506 builder.append(','); 507 stringifier.startNewLine(builder); 508 509 // Append the stringified value. 510 stringifyResult = stringifier.appendStringifiedValue(builder, value, m_object, index); 511 } else { 512 // Get the value. 513 PropertySlot slot(m_object); 514 Identifier& propertyName = m_propertyNames->propertyNameVector()[index]; 515 if (!m_object->getOwnPropertySlot(exec, propertyName, slot)) 516 return true; 517 JSValue value = slot.getValue(exec, propertyName); 518 if (exec->hadException()) 519 return false; 520 521 rollBackPoint = builder.size(); 522 523 // Append the separator string. 524 if (builder[rollBackPoint - 1] != '{') 525 builder.append(','); 526 stringifier.startNewLine(builder); 527 528 // Append the property name. 529 appendQuotedString(builder, propertyName.ustring()); 530 builder.append(':'); 531 if (stringifier.willIndent()) 532 builder.append(' '); 533 534 // Append the stringified value. 535 stringifyResult = stringifier.appendStringifiedValue(builder, value, m_object, propertyName); 536 } 537 538 // From this point on, no access to the this pointer or to any members, because the 539 // Holder object may have moved if the call to stringify pushed a new Holder onto 540 // m_holderStack. 541 542 switch (stringifyResult) { 543 case StringifyFailed: 544 builder.append("null"); 545 break; 546 case StringifySucceeded: 547 break; 548 case StringifyFailedDueToUndefinedValue: 549 // This only occurs when get an undefined value for an object property. 550 // In this case we don't want the separator and property name that we 551 // already appended, so roll back. 552 builder = builder.substr(0, rollBackPoint); 553 break; 554 } 555 556 return true; 557 } 54 558 55 559 // ------------------------------ JSONObject -------------------------------- … … 68 572 { 69 573 const HashEntry* entry = ExecState::jsonTable(exec)->entry(exec, propertyName); 70 71 574 if (!entry) 72 575 return JSObject::getOwnPropertySlot(exec, propertyName, slot); … … 77 580 } 78 581 79 class 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 }; 84 public: 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 141 private: 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 }; 582 void JSONObject::markStringifiers(Stringifier* stringifier) 583 { 584 stringifier->mark(); 585 } 467 586 468 587 // ECMA-262 v5 15.12.3 … … 471 590 if (args.isEmpty()) 472 591 return throwError(exec, GeneralError, "No input to stringify"); 473 474 592 JSValue value = args.at(0); 475 593 JSValue replacer = args.at(1); 476 594 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); 595 return Stringifier(exec, replacer, space).stringify(value); 496 596 } 497 597 -
trunk/JavaScriptCore/runtime/JSONObject.h
r44550 r44813 31 31 namespace JSC { 32 32 33 class Stringifier; 34 33 35 class JSONObject : public JSObject { 34 36 public: 35 37 JSONObject(PassRefPtr<Structure> structure) 36 : JSObject(structure)38 : JSObject(structure) 37 39 { 38 40 } 39 40 virtual bool getOwnPropertySlot(ExecState*, const Identifier&, PropertySlot&);41 42 virtual const ClassInfo* classInfo() const { return &info; }43 static const ClassInfo info;44 41 45 42 static PassRefPtr<Structure> createStructure(JSValue prototype) … … 47 44 return Structure::create(prototype, TypeInfo(ObjectType)); 48 45 } 46 47 static void markStringifiers(Stringifier*); 48 49 private: 50 virtual bool getOwnPropertySlot(ExecState*, const Identifier&, PropertySlot&); 51 52 virtual const ClassInfo* classInfo() const { return &info; } 53 static const ClassInfo info; 49 54 }; 50 55 -
trunk/LayoutTests/ChangeLog
r44811 r44813 1 2009-06-17 Darin Adler <darin@apple.com> 2 3 Reviewed by Oliver Hunt. 4 5 Bug 26429: Make JSON.stringify non-recursive so it can handle objects 6 of arbitrary complexity 7 https://bugs.webkit.org/show_bug.cgi?id=26429 8 9 * fast/js/JSON-stringify-expected.txt: Updated. 10 * fast/js/resources/JSON-stringify.js: Changed the infinite object and 11 infinite array tests to instead just test something a fixed number of 12 levels deep. Otherwise we end up with an infinite loop in the test, 13 which would lead to the slow-script dialog in the production web browser. 14 Also raised the number from 512 to 2048 since there's no fixed limit any more. 15 1 16 2009-06-17 Erik Arvidsson <arv@chromium.org> 2 17 -
trunk/LayoutTests/fast/js/JSON-stringify-expected.txt
r44610 r44813 258 258 function (jsonObject) { 259 259 var deepObject = {}; 260 for (var i = 0; i < 512; i++)260 for (var i = 0; i < 2048; i++) 261 261 deepObject = {next:deepObject}; 262 262 return jsonObject.stringify(deepObject); … … 265 265 function (jsonObject) { 266 266 var deepArray = []; 267 for (var i = 0; i < 512; i++)267 for (var i = 0; i < 2048; i++) 268 268 deepArray = [deepArray]; 269 269 return jsonObject.stringify(deepArray); … … 271 271 PASS tests[i](nativeJSON) is tests[i](JSON) 272 272 function (jsonObject) { 273 function toInfiniteJSONObject() { 273 var depth = 0; 274 function toDeepVirtualJSONObject() { 275 if (++depth >= 2048) 276 return {}; 274 277 var r = {}; 275 r.toJSON = to InfiniteJSONObject;278 r.toJSON = toDeepVirtualJSONObject; 276 279 return {recurse: r}; 277 280 } 278 return jsonObject.stringify(toInfiniteJSONObject()); 279 } 280 PASS tests[i](nativeJSON) threw exception RangeError: Maximum call stack size exceeded.. 281 function (jsonObject) { 282 function toInfiniteJSONArray() { 281 return jsonObject.stringify(toDeepVirtualJSONObject()); 282 } 283 PASS tests[i](nativeJSON) is tests[i](JSON) 284 function (jsonObject) { 285 var depth = 0; 286 function toDeepVirtualJSONArray() { 287 if (++depth >= 2048) 288 return []; 283 289 var r = []; 284 r.toJSON = to InfiniteJSONArray;290 r.toJSON = toDeepJSONArray; 285 291 return [r]; 286 292 } 287 return jsonObject.stringify(toInfiniteJSONArray()); 288 } 289 PASS tests[i](nativeJSON) threw exception RangeError: Maximum call stack size exceeded.. 293 return jsonObject.stringify(toDeepVirtualJSONArray()); 294 } 290 295 function (jsonObject) { 291 296 return jsonObject.stringify(fullCharsetString); -
trunk/LayoutTests/fast/js/resources/JSON-stringify.js
r44610 r44813 241 241 result.push(function(jsonObject){ 242 242 var deepObject = {}; 243 for (var i = 0; i < 512; i++)243 for (var i = 0; i < 2048; i++) 244 244 deepObject = {next:deepObject}; 245 245 return jsonObject.stringify(deepObject); … … 247 247 result.push(function(jsonObject){ 248 248 var deepArray = []; 249 for (var i = 0; i < 512; i++)249 for (var i = 0; i < 2048; i++) 250 250 deepArray = [deepArray]; 251 251 return jsonObject.stringify(deepArray); 252 252 }); 253 253 result.push(function(jsonObject){ 254 function toInfiniteJSONObject() { 254 var depth = 0; 255 function toDeepVirtualJSONObject() { 256 if (++depth >= 2048) 257 return {}; 255 258 var r = {}; 256 r.toJSON = to InfiniteJSONObject;259 r.toJSON = toDeepVirtualJSONObject; 257 260 return {recurse: r}; 258 261 } 259 return jsonObject.stringify(toInfiniteJSONObject()); 260 }); 261 result[result.length - 1].throws = true; 262 result.push(function(jsonObject){ 263 function toInfiniteJSONArray() { 262 return jsonObject.stringify(toDeepVirtualJSONObject()); 263 }); 264 result.push(function(jsonObject){ 265 var depth = 0; 266 function toDeepVirtualJSONArray() { 267 if (++depth >= 2048) 268 return []; 264 269 var r = []; 265 r.toJSON = to InfiniteJSONArray;270 r.toJSON = toDeepJSONArray; 266 271 return [r]; 267 272 } 268 return jsonObject.stringify(toInfiniteJSONArray()); 269 }); 270 result[result.length - 1].throws = true; 273 return jsonObject.stringify(toDeepVirtualJSONArray()); 274 }); 271 275 var fullCharsetString = ""; 272 276 for (var i = 0; i < 65536; i++) -
trunk/WebKitTools/DumpRenderTree/mac/DumpRenderTreeWindow.mm
r44425 r44813 71 71 CFRange arrayRange = CFRangeMake(0, CFArrayGetCount(openWindowsRef)); 72 72 CFIndex i = CFArrayGetFirstIndexOfValue(openWindowsRef, arrayRange, self); 73 assert(i != -1);74 CFArrayRemoveValueAtIndex(openWindowsRef, i);75 73 if (i != kCFNotFound) 74 CFArrayRemoveValueAtIndex(openWindowsRef, i); 75 76 76 [super close]; 77 77 }
Note: See TracChangeset
for help on using the changeset viewer.