Changeset 44813 in webkit


Ignore:
Timestamp:
Jun 18, 2009 12:18:12 PM (15 years ago)
Author:
Darin Adler
Message:

JavaScriptCore:

2009-06-17 Darin Adler <Darin Adler>

Reviewed by Oliver Hunt.

Bug 26429: Make JSON.stringify non-recursive so it can handle objects
of arbitrary complexity
https://bugs.webkit.org/show_bug.cgi?id=26429

For marking I decided not to use gcProtect, because this is inside the engine
so it's easy enough to just do marking. And that darned gcProtect does locking!
Oliver tried to convince me to used MarkedArgumentBuffer, but the constructor
for that class says "FIXME: Remove all clients of this API, then remove this API."

  • runtime/Collector.cpp: (JSC::Heap::collect): Add a call to JSONObject::markStringifiers.
  • runtime/CommonIdentifiers.cpp: (JSC::CommonIdentifiers::CommonIdentifiers): Added emptyIdentifier.
  • runtime/CommonIdentifiers.h: Ditto.
  • runtime/JSGlobalData.cpp: (JSC::JSGlobalData::JSGlobalData): Initialize firstStringifierToMark to 0.
  • runtime/JSGlobalData.h: Added firstStringifierToMark.
  • runtime/JSONObject.cpp: Cut down the includes to the needed ones only. (JSC::unwrapNumberOrString): Added. Helper for unwrapping number and string objects to get their number and string values. (JSC::ReplacerPropertyName::ReplacerPropertyName): Added. The class is used to wrap an identifier or integer so we don't have to do any work unless we actually call a replacer. (JSC::ReplacerPropertyName::value): Added. (JSC::gap): Added. Helper function for the Stringifier constructor. (JSC::PropertyNameForFunctionCall::PropertyNameForFunctionCall): Added. The class is used to wrap an identifier or integer so we don't have to allocate a number or string until we actually call toJSON or a replacer. (JSC::PropertyNameForFunctionCall::asJSValue): Added. (JSC::Stringifier::Stringifier): Updated and moved out of the class definition. Added code to hook this into a singly linked list for marking. (JSC::Stringifier::~Stringifier): Remove from the singly linked list. (JSC::Stringifier::mark): Mark all the objects in the holder stacks. (JSC::Stringifier::stringify): Updated. (JSC::Stringifier::appendQuotedString): Tweaked and streamlined a bit. (JSC::Stringifier::toJSON): Renamed from toJSONValue. (JSC::Stringifier::appendStringifiedValue): Renamed from stringify. Added code to use the m_holderStack to do non-recursive stringify of objects and arrays. This code also uses the timeout checker since in pathological cases it could be slow even without calling into the JavaScript virtual machine. (JSC::Stringifier::willIndent): Added. (JSC::Stringifier::indent): Added. (JSC::Stringifier::unindent): Added. (JSC::Stringifier::startNewLine): Added. (JSC::Stringifier::Holder::Holder): Added. (JSC::Stringifier::Holder::appendNextProperty): Added. This is the function that handles the format of arrays and objects. (JSC::JSONObject::getOwnPropertySlot): Moved this down to the bottom of the file so the JSONObject class is not interleaved with the Stringifier class. (JSC::JSONObject::markStringifiers): Added. Calls mark. (JSC::JSONProtoFuncStringify): Streamlined the code here. The code to compute the gap string is now a separate function.
  • runtime/JSONObject.h: Made everything private. Added markStringifiers.

LayoutTests:

2009-06-17 Darin Adler <Darin Adler>

Reviewed by Oliver Hunt.

Bug 26429: Make JSON.stringify non-recursive so it can handle objects
of arbitrary complexity
https://bugs.webkit.org/show_bug.cgi?id=26429

  • fast/js/JSON-stringify-expected.txt: Updated.
  • fast/js/resources/JSON-stringify.js: Changed the infinite object and infinite array tests to instead just test something a fixed number of levels deep. Otherwise we end up with an infinite loop in the test, which would lead to the slow-script dialog in the production web browser. Also raised the number from 512 to 2048 since there's no fixed limit any more.
Location:
trunk
Files:
12 edited

Legend:

Unmodified
Added
Removed
  • trunk/JavaScriptCore/ChangeLog

    r44797 r44813  
     12009-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
    1652009-06-17  Oliver Hunt  <oliver@apple.com>
    266
  • trunk/JavaScriptCore/runtime/Collector.cpp

    r44224 r44813  
    2828#include "JSGlobalObject.h"
    2929#include "JSLock.h"
     30#include "JSONObject.h"
    3031#include "JSString.h"
    3132#include "JSValue.h"
     
    992993    if (m_globalData->scopeNodeBeingReparsed)
    993994        m_globalData->scopeNodeBeingReparsed->mark();
     995    if (m_globalData->firstStringifierToMark)
     996        JSONObject::markStringifiers(m_globalData->firstStringifierToMark);
    994997
    995998    JAVASCRIPTCORE_GC_MARKED();
  • trunk/JavaScriptCore/runtime/CommonIdentifiers.cpp

    r44224 r44813  
    11/*
    2  *  Copyright (C) 2003, 2007 Apple Inc. All rights reserved.
     2 *  Copyright (C) 2003, 2007, 2009 Apple Inc. All rights reserved.
    33 *
    44 *  This library is free software; you can redistribute it and/or
     
    2424namespace JSC {
    2525
    26 const char* const nullCString = 0;
     26static const char* const nullCString = 0;
    2727
    2828#define INITIALIZE_PROPERTY_NAME(name) , name(globalData, #name)
     
    3030CommonIdentifiers::CommonIdentifiers(JSGlobalData* globalData)
    3131    : nullIdentifier(globalData, nullCString)
     32    , emptyIdentifier(globalData, "")
    3233    , underscoreProto(globalData, "__proto__")
    3334    , thisIdentifier(globalData, "this")
  • trunk/JavaScriptCore/runtime/CommonIdentifiers.h

    r44550 r44813  
    11/*
    2  *  Copyright (C) 2003,2007 Apple Computer, Inc
     2 *  Copyright (C) 2003, 2007, 2009 Apple Inc. All rights reserved.
    33 *
    44 *  This library is free software; you can redistribute it and/or
     
    7777    public:
    7878        const Identifier nullIdentifier;
     79        const Identifier emptyIdentifier;
    7980        const Identifier underscoreProto;
    8081        const Identifier thisIdentifier;
  • trunk/JavaScriptCore/runtime/JSGlobalData.cpp

    r44550 r44813  
    140140    , dynamicGlobalObject(0)
    141141    , scopeNodeBeingReparsed(0)
     142    , firstStringifierToMark(0)
    142143{
    143144#if PLATFORM(MAC)
  • trunk/JavaScriptCore/runtime/JSGlobalData.h

    r44705 r44813  
    5555    class Parser;
    5656    class ScopeNode;
     57    class Stringifier;
    5758    class Structure;
    5859    class UString;
     60
    5961    struct HashTable;
    6062    struct VPtrSet;
     
    147149
    148150        ScopeNode* scopeNodeBeingReparsed;
     151        Stringifier* firstStringifierToMark;
    149152
    150153    private:
  • trunk/JavaScriptCore/runtime/JSONObject.cpp

    r44610 r44813  
    2727#include "JSONObject.h"
    2828
     29#include "Error.h"
    2930#include "ExceptionHelpers.h"
    3031#include "JSArray.h"
    31 #include "JSFunction.h"
    32 #include "LiteralParser.h"
    33 #include "Nodes.h"
    3432#include "PropertyNameArray.h"
    35 
    36 #include "ObjectPrototype.h"
    37 #include "Operations.h"
    38 #include <time.h>
    39 #include <wtf/Assertions.h>
    4033#include <wtf/MathExtras.h>
    4134
    42 
    4335namespace JSC {
    4436
     
    5244
    5345namespace JSC {
     46
     47// PropertyNameForFunctionCall objects must be on the stack, since the JSValue that they create is not marked.
     48class PropertyNameForFunctionCall {
     49public:
     50    PropertyNameForFunctionCall(const Identifier&);
     51    PropertyNameForFunctionCall(unsigned);
     52
     53    JSValue value(ExecState*) const;
     54
     55private:
     56    const Identifier* m_identifier;
     57    unsigned m_number;
     58    mutable JSValue m_value;
     59};
     60
     61class Stringifier : Noncopyable {
     62public:
     63    Stringifier(ExecState*, JSValue replacer, JSValue space);
     64    ~Stringifier();
     65    JSValue stringify(JSValue);
     66
     67    void mark();
     68
     69private:
     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
     120static 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
     129static 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
     156inline PropertyNameForFunctionCall::PropertyNameForFunctionCall(const Identifier& identifier)
     157    : m_identifier(&identifier)
     158{
     159}
     160
     161inline PropertyNameForFunctionCall::PropertyNameForFunctionCall(unsigned number)
     162    : m_identifier(0)
     163    , m_number(number)
     164{
     165}
     166
     167JSValue 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
     180Stringifier::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
     215Stringifier::~Stringifier()
     216{
     217    ASSERT(m_exec->globalData().firstStringifierToMark == this);
     218    m_exec->globalData().firstStringifierToMark = m_nextStringifierToMark;
     219}
     220
     221void 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
     233JSValue 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
     251void 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
     309inline 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
     333Stringifier::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
     415inline bool Stringifier::willIndent() const
     416{
     417    return !m_gap.isEmpty();
     418}
     419
     420inline 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
     430inline 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
     436inline 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
     444inline Stringifier::Holder::Holder(JSObject* object)
     445    : m_object(object)
     446    , m_isArray(object->inherits(&JSArray::info))
     447    , m_index(0)
     448{
     449}
     450
     451bool 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}
    54558
    55559// ------------------------------ JSONObject --------------------------------
     
    68572{
    69573    const HashEntry* entry = ExecState::jsonTable(exec)->entry(exec, propertyName);
    70 
    71574    if (!entry)
    72575        return JSObject::getOwnPropertySlot(exec, propertyName, slot);
     
    77580}
    78581
    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 };
     582void JSONObject::markStringifiers(Stringifier* stringifier)
     583{
     584    stringifier->mark();
     585}
    467586
    468587// ECMA-262 v5 15.12.3
     
    471590    if (args.isEmpty())
    472591        return throwError(exec, GeneralError, "No input to stringify");
    473 
    474592    JSValue value = args.at(0);
    475593    JSValue replacer = args.at(1);
    476594    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);
    496596}
    497597
  • trunk/JavaScriptCore/runtime/JSONObject.h

    r44550 r44813  
    3131namespace JSC {
    3232
     33    class Stringifier;
     34
    3335    class JSONObject : public JSObject {
    3436    public:
    3537        JSONObject(PassRefPtr<Structure> structure)
    36             :JSObject(structure)
     38            : JSObject(structure)
    3739        {
    3840        }
    39 
    40         virtual bool getOwnPropertySlot(ExecState*, const Identifier&, PropertySlot&);
    41 
    42         virtual const ClassInfo* classInfo() const { return &info; }
    43         static const ClassInfo info;
    4441
    4542        static PassRefPtr<Structure> createStructure(JSValue prototype)
     
    4744            return Structure::create(prototype, TypeInfo(ObjectType));
    4845        }
     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;
    4954    };
    5055
  • trunk/LayoutTests/ChangeLog

    r44811 r44813  
     12009-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
    1162009-06-17  Erik Arvidsson  <arv@chromium.org>
    217
  • trunk/LayoutTests/fast/js/JSON-stringify-expected.txt

    r44610 r44813  
    258258function (jsonObject) {
    259259        var deepObject = {};
    260         for (var i = 0; i < 512; i++)
     260        for (var i = 0; i < 2048; i++)
    261261            deepObject = {next:deepObject};
    262262        return jsonObject.stringify(deepObject);
     
    265265function (jsonObject) {
    266266        var deepArray = [];
    267         for (var i = 0; i < 512; i++)
     267        for (var i = 0; i < 2048; i++)
    268268            deepArray = [deepArray];
    269269        return jsonObject.stringify(deepArray);
     
    271271PASS tests[i](nativeJSON) is tests[i](JSON)
    272272function (jsonObject) {
    273         function toInfiniteJSONObject() {
     273        var depth = 0;
     274        function toDeepVirtualJSONObject() {
     275            if (++depth >= 2048)
     276                return {};
    274277            var r = {};
    275             r.toJSON = toInfiniteJSONObject;
     278            r.toJSON = toDeepVirtualJSONObject;
    276279            return {recurse: r};
    277280        }
    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    }
     283PASS tests[i](nativeJSON) is tests[i](JSON)
     284function (jsonObject) {
     285        var depth = 0;
     286        function toDeepVirtualJSONArray() {
     287            if (++depth >= 2048)
     288                return [];
    283289            var r = [];
    284             r.toJSON = toInfiniteJSONArray;
     290            r.toJSON = toDeepJSONArray;
    285291            return [r];
    286292        }
    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    }
    290295function (jsonObject) {
    291296        return jsonObject.stringify(fullCharsetString);
  • trunk/LayoutTests/fast/js/resources/JSON-stringify.js

    r44610 r44813  
    241241    result.push(function(jsonObject){
    242242        var deepObject = {};
    243         for (var i = 0; i < 512; i++)
     243        for (var i = 0; i < 2048; i++)
    244244            deepObject = {next:deepObject};
    245245        return jsonObject.stringify(deepObject);
     
    247247    result.push(function(jsonObject){
    248248        var deepArray = [];
    249         for (var i = 0; i < 512; i++)
     249        for (var i = 0; i < 2048; i++)
    250250            deepArray = [deepArray];
    251251        return jsonObject.stringify(deepArray);
    252252    });
    253253    result.push(function(jsonObject){
    254         function toInfiniteJSONObject() {
     254        var depth = 0;
     255        function toDeepVirtualJSONObject() {
     256            if (++depth >= 2048)
     257                return {};
    255258            var r = {};
    256             r.toJSON = toInfiniteJSONObject;
     259            r.toJSON = toDeepVirtualJSONObject;
    257260            return {recurse: r};
    258261        }
    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 [];
    264269            var r = [];
    265             r.toJSON = toInfiniteJSONArray;
     270            r.toJSON = toDeepJSONArray;
    266271            return [r];
    267272        }
    268         return jsonObject.stringify(toInfiniteJSONArray());
    269     });
    270     result[result.length - 1].throws = true;
     273        return jsonObject.stringify(toDeepVirtualJSONArray());
     274    });
    271275    var fullCharsetString = "";
    272276    for (var i = 0; i < 65536; i++)
  • trunk/WebKitTools/DumpRenderTree/mac/DumpRenderTreeWindow.mm

    r44425 r44813  
    7171    CFRange arrayRange = CFRangeMake(0, CFArrayGetCount(openWindowsRef));
    7272    CFIndex i = CFArrayGetFirstIndexOfValue(openWindowsRef, arrayRange, self);
    73     assert(i != -1);
    74     CFArrayRemoveValueAtIndex(openWindowsRef, i);
    75    
     73    if (i != kCFNotFound)
     74        CFArrayRemoveValueAtIndex(openWindowsRef, i);
     75
    7676    [super close];
    7777}
Note: See TracChangeset for help on using the changeset viewer.