Commit 862c90b4 authored by oliver@apple.com's avatar oliver@apple.com

Bug 26249: Support JSON.stringify

<https://bugs.webkit.org/show_bug.cgi?id=26249>

Reviewed by Sam Weinig.

Implement JSON.stringify.  This patch handles all the semantics of the ES5
JSON.stringify function, including replacer functions and arrays and both
string and numeric gap arguments.

Currently uses a clamped recursive algorithm basically identical to the spec
description but with a few minor tweaks for performance and corrected semantics
discussed in the es-discuss mailing list.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@44550 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 9056afec
2009-06-07 Oliver Hunt <oliver@apple.com>
Reviewed by Sam Weinig.
Bug 26249: Support JSON.stringify
<https://bugs.webkit.org/show_bug.cgi?id=26249>
Implement JSON.stringify. This patch handles all the semantics of the ES5
JSON.stringify function, including replacer functions and arrays and both
string and numeric gap arguments.
Currently uses a clamped recursive algorithm basically identical to the spec
description but with a few minor tweaks for performance and corrected semantics
discussed in the es-discuss mailing list.
* DerivedSources.make:
* GNUmakefile.am:
* JavaScriptCore.pri:
* JavaScriptCore.vcproj/JavaScriptCore/JavaScriptCore.vcproj:
* JavaScriptCore.xcodeproj/project.pbxproj:
* interpreter/CallFrame.h:
(JSC::ExecState::jsonTable):
* runtime/CommonIdentifiers.h:
add toJSON to the list of common identifiers
* runtime/JSGlobalData.cpp:
(JSC::JSGlobalData::JSGlobalData):
(JSC::JSGlobalData::~JSGlobalData):
* runtime/JSGlobalData.h:
* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::reset):
Add support for the JSON object lookup table
* runtime/JSONObject.cpp: Added.
(JSC::):
(JSC::JSONObject::getOwnPropertySlot):
(JSC::Stringifier::):
(JSC::Stringifier::Stringifier):
(JSC::Stringifier::stringify):
(JSC::Stringifier::appendString):
(JSC::Stringifier::StringKeyGenerator::StringKeyGenerator):
(JSC::Stringifier::StringKeyGenerator::getKey):
(JSC::Stringifier::IntKeyGenerator::IntKeyGenerator):
(JSC::Stringifier::IntKeyGenerator::getKey):
These KeyGenerator classes are used to abstract away the lazy evaluation of keys for
toJSON and replacer functions.
(JSC::Stringifier::toJSONValue):
(JSC::Stringifier::stringifyArray):
(JSC::Stringifier::stringifyObject):
(JSC::JSONProtoFuncStringify):
* runtime/JSONObject.h: Added.
(JSC::JSONObject:::JSObject):
(JSC::JSONObject::classInfo):
(JSC::JSONObject::createStructure):
2009-06-09 Gavin Barraclough <barraclough@apple.com>
Reviewed by Geoff Garen.
......@@ -40,6 +40,7 @@ all : \
chartables.c \
DatePrototype.lut.h \
Grammar.cpp \
JSONObject.lut.h \
Lexer.lut.h \
MathObject.lut.h \
NumberConstructor.lut.h \
......
......@@ -35,6 +35,7 @@ javascriptcore_built_nosources += \
DerivedSources/Lexer.lut.h \
JavaScriptCore/runtime/ArrayPrototype.lut.h \
JavaScriptCore/runtime/DatePrototype.lut.h \
JavaScriptCore/runtime/JSONObject.lut.h \
JavaScriptCore/runtime/MathObject.lut.h \
JavaScriptCore/runtime/NumberConstructor.lut.h \
JavaScriptCore/runtime/RegExpConstructor.lut.h \
......@@ -182,6 +183,8 @@ javascriptcore_sources += \
JavaScriptCore/runtime/JSGlobalData.h \
JavaScriptCore/runtime/JSNotAnObject.cpp \
JavaScriptCore/runtime/JSNotAnObject.h \
JavaScriptCore/runtime/JSONObject.cpp \
JavaScriptCore/runtime/JSONObject.h \
JavaScriptCore/runtime/JSPropertyNameIterator.cpp \
JavaScriptCore/runtime/JSPropertyNameIterator.h \
JavaScriptCore/runtime/LiteralParser.cpp \
......@@ -590,6 +593,7 @@ javascriptcore_dist += \
CLEANFILES += \
JavaScriptCore/runtime/ArrayPrototype.lut.h \
JavaScriptCore/runtime/DatePrototype.lut.h \
JavaScriptCore/runtime/JSONObject.lut.h \
JavaScriptCore/runtime/MathObject.lut.h \
JavaScriptCore/runtime/NumberConstructor.lut.h \
JavaScriptCore/runtime/RegExpConstructor.lut.h \
......
......@@ -110,6 +110,7 @@ SOURCES += \
runtime/JSVariableObject.cpp \
runtime/JSActivation.cpp \
runtime/JSNotAnObject.cpp \
runtime/JSONObject.cpp \
runtime/LiteralParser.cpp \
runtime/TimeoutChecker.cpp \
bytecode/CodeBlock.cpp \
......
......@@ -794,6 +794,13 @@
<File
RelativePath="..\..\runtime\JSNumberCell.h"
>
<File
RelativePath="..\..\runtime\JSONObject.cpp"
>
</File>
<File
RelativePath="..\..\runtime\JSONObject.h"
>
</File>
<File
RelativePath="..\..\runtime\JSObject.cpp"
......
......@@ -198,6 +198,9 @@
A7B48F490EE8936F00DCBDB6 /* ExecutableAllocator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7B48DB60EE74CFC00DCBDB6 /* ExecutableAllocator.cpp */; };
A7E2EA6B0FB460CF00601F06 /* LiteralParser.h in Headers */ = {isa = PBXBuildFile; fileRef = A7E2EA690FB460CF00601F06 /* LiteralParser.h */; };
A7E2EA6C0FB460CF00601F06 /* LiteralParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7E2EA6A0FB460CF00601F06 /* LiteralParser.cpp */; };
A7F9935F0FD7325100A0B2D0 /* JSONObject.h in Headers */ = {isa = PBXBuildFile; fileRef = A7F9935D0FD7325100A0B2D0 /* JSONObject.h */; };
A7F993600FD7325100A0B2D0 /* JSONObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7F9935E0FD7325100A0B2D0 /* JSONObject.cpp */; };
A7F9949B0FD746A300A0B2D0 /* JSONObject.lut.h in Headers */ = {isa = PBXBuildFile; fileRef = A7F9949A0FD746A300A0B2D0 /* JSONObject.lut.h */; };
BC02E90D0E1839DB000F9297 /* ErrorConstructor.h in Headers */ = {isa = PBXBuildFile; fileRef = BC02E9050E1839DB000F9297 /* ErrorConstructor.h */; };
BC02E90F0E1839DB000F9297 /* ErrorPrototype.h in Headers */ = {isa = PBXBuildFile; fileRef = BC02E9070E1839DB000F9297 /* ErrorPrototype.h */; };
BC02E9110E1839DB000F9297 /* NativeErrorConstructor.h in Headers */ = {isa = PBXBuildFile; fileRef = BC02E9090E1839DB000F9297 /* NativeErrorConstructor.h */; };
......@@ -734,6 +737,9 @@
A7E42C190E3938830065A544 /* JSStaticScopeObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSStaticScopeObject.cpp; sourceTree = "<group>"; };
A7F8690E0F9584A100558697 /* CachedCall.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CachedCall.h; sourceTree = "<group>"; };
A7F869EC0F95C2EC00558697 /* CallFrameClosure.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallFrameClosure.h; sourceTree = "<group>"; };
A7F9935D0FD7325100A0B2D0 /* JSONObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONObject.h; sourceTree = "<group>"; };
A7F9935E0FD7325100A0B2D0 /* JSONObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSONObject.cpp; sourceTree = "<group>"; };
A7F9949A0FD746A300A0B2D0 /* JSONObject.lut.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JSONObject.lut.h; path = /Users/oliver/builds/Debug/DerivedSources/JavaScriptCore/JSONObject.lut.h; sourceTree = "<absolute>"; };
A8E894310CD0602400367179 /* JSCallbackObjectFunctions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSCallbackObjectFunctions.h; sourceTree = "<group>"; };
A8E894330CD0603F00367179 /* JSGlobalObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSGlobalObject.h; sourceTree = "<group>"; };
BC02E9040E1839DB000F9297 /* ErrorConstructor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ErrorConstructor.cpp; sourceTree = "<group>"; };
......@@ -1142,6 +1148,7 @@
children = (
BC18C5230E16FC8A00B34460 /* ArrayPrototype.lut.h */,
BCD203E70E1718F4002C7E82 /* DatePrototype.lut.h */,
A7F9949A0FD746A300A0B2D0 /* JSONObject.lut.h */,
BC18C5290E16FCC200B34460 /* MathObject.lut.h */,
BC2680E60E16D52300A06E92 /* NumberConstructor.lut.h */,
BCD202D50E170708002C7E82 /* RegExpConstructor.lut.h */,
......@@ -1462,6 +1469,8 @@
6507D2970E871E4A00D7D896 /* TypeInfo.h */,
F692A8850255597D01FF60F7 /* UString.cpp */,
F692A8860255597D01FF60F7 /* UString.h */,
A7F9935D0FD7325100A0B2D0 /* JSONObject.h */,
A7F9935E0FD7325100A0B2D0 /* JSONObject.cpp */,
);
path = runtime;
sourceTree = "<group>";
......@@ -1846,6 +1855,8 @@
960626960FB8EC02009798AB /* JITStubCall.h in Headers */,
0BDFFAE00FC6192900D69EF4 /* CrossThreadRefCounted.h in Headers */,
0BDFFAE10FC6193100D69EF4 /* OwnFastMallocPtr.h in Headers */,
A7F9935F0FD7325100A0B2D0 /* JSONObject.h in Headers */,
A7F9949B0FD746A300A0B2D0 /* JSONObject.lut.h in Headers */,
86ADD1450FDDEA980006EEC2 /* ARMv7Assembler.h in Headers */,
86ADD1460FDDEA980006EEC2 /* MacroAssemblerARMv7.h in Headers */,
);
......@@ -2209,6 +2220,7 @@
A7E2EA6C0FB460CF00601F06 /* LiteralParser.cpp in Sources */,
93052C340FB792190048FDC3 /* ParserArena.cpp in Sources */,
BCDD51EB0FB8DF74004A8BDC /* JITOpcodes.cpp in Sources */,
A7F993600FD7325100A0B2D0 /* JSONObject.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......
......@@ -89,6 +89,7 @@ namespace JSC {
#endif
static const HashTable* arrayTable(CallFrame* callFrame) { return callFrame->globalData().arrayTable; }
static const HashTable* dateTable(CallFrame* callFrame) { return callFrame->globalData().dateTable; }
static const HashTable* jsonTable(CallFrame* callFrame) { return callFrame->globalData().jsonTable; }
static const HashTable* mathTable(CallFrame* callFrame) { return callFrame->globalData().mathTable; }
static const HashTable* numberTable(CallFrame* callFrame) { return callFrame->globalData().numberTable; }
static const HashTable* regExpTable(CallFrame* callFrame) { return callFrame->globalData().regExpTable; }
......
......@@ -59,6 +59,7 @@
macro(test) \
macro(toExponential) \
macro(toFixed) \
macro(toJSON) \
macro(toLocaleString) \
macro(toPrecision) \
macro(toString) \
......
......@@ -60,6 +60,7 @@ using namespace WTF;
namespace JSC {
extern const HashTable arrayTable;
extern const HashTable jsonTable;
extern const HashTable dateTable;
extern const HashTable mathTable;
extern const HashTable numberTable;
......@@ -105,6 +106,7 @@ JSGlobalData::JSGlobalData(bool isShared, const VPtrSet& vptrSet)
, clientData(0)
, arrayTable(fastNew<HashTable>(JSC::arrayTable))
, dateTable(fastNew<HashTable>(JSC::dateTable))
, jsonTable(fastNew<HashTable>(JSC::jsonTable))
, mathTable(fastNew<HashTable>(JSC::mathTable))
, numberTable(fastNew<HashTable>(JSC::numberTable))
, regExpTable(fastNew<HashTable>(JSC::regExpTable))
......@@ -155,6 +157,7 @@ JSGlobalData::~JSGlobalData()
arrayTable->deleteTable();
dateTable->deleteTable();
jsonTable->deleteTable();
mathTable->deleteTable();
numberTable->deleteTable();
regExpTable->deleteTable();
......@@ -166,6 +169,7 @@ JSGlobalData::~JSGlobalData()
fastDelete(const_cast<HashTable*>(arrayTable));
fastDelete(const_cast<HashTable*>(dateTable));
fastDelete(const_cast<HashTable*>(jsonTable));
fastDelete(const_cast<HashTable*>(mathTable));
fastDelete(const_cast<HashTable*>(numberTable));
fastDelete(const_cast<HashTable*>(regExpTable));
......
......@@ -82,6 +82,7 @@ namespace JSC {
const HashTable* arrayTable;
const HashTable* dateTable;
const HashTable* jsonTable;
const HashTable* mathTable;
const HashTable* numberTable;
const HashTable* regExpTable;
......
......@@ -50,6 +50,7 @@
#include "JSFunction.h"
#include "JSGlobalObjectFunctions.h"
#include "JSLock.h"
#include "JSONObject.h"
#include "Interpreter.h"
#include "MathObject.h"
#include "NativeErrorConstructor.h"
......@@ -318,7 +319,8 @@ void JSGlobalObject::reset(JSValue prototype)
GlobalPropertyInfo(Identifier(exec, "Math"), new (exec) MathObject(exec, MathObject::createStructure(d()->objectPrototype)), DontEnum | DontDelete),
GlobalPropertyInfo(Identifier(exec, "NaN"), jsNaN(exec), DontEnum | DontDelete),
GlobalPropertyInfo(Identifier(exec, "Infinity"), jsNumber(exec, Inf), DontEnum | DontDelete),
GlobalPropertyInfo(Identifier(exec, "undefined"), jsUndefined(), DontEnum | DontDelete)
GlobalPropertyInfo(Identifier(exec, "undefined"), jsUndefined(), DontEnum | DontDelete),
GlobalPropertyInfo(Identifier(exec, "JSON"), new (exec) JSONObject(JSONObject::createStructure(d()->objectPrototype)), DontEnum | DontDelete)
};
addStaticGlobals(staticGlobals, sizeof(staticGlobals) / sizeof(GlobalPropertyInfo));
......
/*
* Copyright (C) 2009 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "JSONObject.h"
#include "ExceptionHelpers.h"
#include "JSArray.h"
#include "JSFunction.h"
#include "LiteralParser.h"
#include "Nodes.h"
#include "PropertyNameArray.h"
#include "ObjectPrototype.h"
#include "Operations.h"
#include <time.h>
#include <wtf/Assertions.h>
#include <wtf/MathExtras.h>
namespace JSC {
ASSERT_CLASS_FITS_IN_CELL(JSONObject);
static JSValue JSC_HOST_CALL JSONProtoFuncStringify(ExecState*, JSObject*, JSValue, const ArgList&);
}
#include "JSONObject.lut.h"
namespace JSC {
// ------------------------------ JSONObject --------------------------------
const ClassInfo JSONObject::info = { "JSON", 0, 0, ExecState::jsonTable };
/* Source for JSONObject.lut.h
@begin jsonTable
stringify JSONProtoFuncStringify DontEnum|Function 1
@end
*/
// ECMA 15.8
bool JSONObject::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot)
{
const HashEntry* entry = ExecState::jsonTable(exec)->entry(exec, propertyName);
if (!entry)
return JSObject::getOwnPropertySlot(exec, propertyName, slot);
ASSERT(entry->attributes() & Function);
setUpStaticFunctionSlot(exec, entry, this, propertyName, slot);
return true;
}
class Stringifier {
typedef UString StringBuilder;
// <https://bugs.webkit.org/show_bug.cgi?id=26276> arbitrary limits for recursion
// are bad
enum { MaxObjectDepth = 1600 };
public:
Stringifier(ExecState* exec, JSValue replacer, const UString& gap)
: m_exec(exec)
, m_replacer(replacer)
, m_gap(gap)
, m_arrayReplacerPropertyNames(exec)
, m_usingArrayReplacer(false)
, m_replacerCallType(CallTypeNone)
{
if (m_replacer.isObject()) {
if (asObject(m_replacer)->inherits(&JSArray::info)) {
JSObject* array = asObject(m_replacer);
bool isRealJSArray = isJSArray(&m_exec->globalData(), m_replacer);
unsigned length = array->get(m_exec, m_exec->globalData().propertyNames->length).toUInt32(m_exec);
for (unsigned i = 0; i < length; i++) {
JSValue name;
if (isRealJSArray && asArray(array)->canGetIndex(i))
name = asArray(array)->getIndex(i);
else {
name = array->get(m_exec, i);
if (exec->hadException())
break;
}
if (!name.isString())
continue;
UString propertyName = name.toString(m_exec);
if (exec->hadException())
return;
m_arrayReplacerPropertyNames.add(Identifier(m_exec, propertyName));
}
m_usingArrayReplacer = true;
} else
m_replacerCallType = asObject(m_replacer)->getCallData(m_replacerCallData);
}
}
JSValue stringify(JSValue value)
{
JSObject* obj = constructEmptyObject(m_exec);
if (m_exec->hadException())
return jsNull();
Identifier emptyIdent(m_exec, "");
obj->putDirect(emptyIdent, value);
StringKeyGenerator key(emptyIdent);
value = toJSONValue(key, value);
if (m_exec->hadException())
return jsNull();
StringBuilder builder;
if (!stringify(builder, obj, key, value))
return jsUndefined();
return (m_exec->hadException())? m_exec->exception() : jsString(m_exec, builder);
}
private:
void appendString(StringBuilder& builder, const UString& value)
{
int length = value.size();
const UChar* data = value.data();
builder.reserveCapacity(builder.size() + length + 2 + 8); // +2 for "'s +8 for a buffer
builder.append('\"');
int index = 0;
while (index < length) {
int start = index;
while (index < length && (data[index] > 0x1f && data[index] != '"' && data[index] != '\\'))
index++;
builder.append(data + start, index - start);
if (index < length) {
switch(data[index]){
case '\t': {
UChar tab[] = {'\\','t'};
builder.append(tab, 2);
break;
}
case '\r': {
UChar cr[] = {'\\','r'};
builder.append(cr, 2);
break;
}
case '\n': {
UChar nl[] = {'\\','n'};
builder.append(nl, 2);
break;
}
case '\f': {
UChar tab[] = {'\\','f'};
builder.append(tab, 2);
break;
}
case '\b': {
UChar bs[] = {'\\','b'};
builder.append(bs, 2);
break;
}
case '\"': {
UChar quote[] = {'\\','"'};
builder.append(quote, 2);
break;
}
case '\\': {
UChar slash[] = {'\\', '\\'};
builder.append(slash, 2);
break;
}
default:
int ch = (int)data[index];
static const char* hexStr = "0123456789abcdef";
UChar oct[] = {'\\', 'u', hexStr[(ch >> 12) & 15], hexStr[(ch >> 8) & 15], hexStr[(ch >> 4) & 15], hexStr[(ch >> 0) & 15]};
builder.append(oct, sizeof(oct) / sizeof(UChar));
break;
}
index++;
}
}
builder.append('\"');
}
class StringKeyGenerator {
public:
StringKeyGenerator(const Identifier& property)
: m_property(property)
{
}
JSValue getKey(ExecState* exec) { if (!m_key) m_key = jsString(exec, m_property.ustring()); return m_key; }
private:
Identifier m_property;
JSValue m_key;
};
class IntKeyGenerator {
public:
IntKeyGenerator(uint32_t property)
: m_property(property)
{
}
JSValue getKey(ExecState* exec) { if (!m_key) m_key = jsNumber(exec, m_property); return m_key; }
private:
uint32_t m_property;
JSValue m_key;
};
template <typename KeyGenerator> JSValue toJSONValue(KeyGenerator& jsKey, JSValue jsValue)
{
ASSERT(!m_exec->hadException());
if (!jsValue.isObject() || !asObject(jsValue)->hasProperty(m_exec, m_exec->globalData().propertyNames->toJSON))
return jsValue;
JSValue jsToJSON = asObject(jsValue)->get(m_exec, m_exec->globalData().propertyNames->toJSON);
if (!jsToJSON.isObject())
return jsValue;
if (m_exec->hadException())
return jsNull();
JSObject* object = asObject(jsToJSON);
CallData callData;
CallType callType = object->getCallData(callData);
if (callType == CallTypeNone)
return jsValue;
JSValue list[] = { jsKey.getKey(m_exec) };
ArgList args(list, sizeof(list) / sizeof(JSValue));
return call(m_exec, object, callType, callData, jsValue, args);
}
bool stringifyArray(StringBuilder& builder, JSArray* array)
{
if (m_objectStack.contains(array)) {
throwError(m_exec, TypeError);
return false;
}
if (m_objectStack.size() > MaxObjectDepth) {
m_exec->setException(createStackOverflowError(m_exec));
return false;
}
m_objectStack.add(array);
UString stepBack = m_indent;
m_indent.append(m_gap);
Vector<StringBuilder, 16> partial;
unsigned len = array->get(m_exec, m_exec->globalData().propertyNames->length).toUInt32(m_exec);
bool isRealJSArray = isJSArray(&m_exec->globalData(), array);
for (unsigned i = 0; i < len; i++) {
JSValue value;
if (isRealJSArray && array->canGetIndex(i))
value = array->getIndex(i);
else {
value = array->get(m_exec, i);
if (m_exec->hadException())
return false;
}
StringBuilder result;
IntKeyGenerator key(i);
value = toJSONValue(key, value);
if (partial.size() && m_gap.isEmpty())
partial.last().append(',');
if (!stringify(result, array, key, value) && !m_exec->hadException())
result.append("null");
else if (m_exec->hadException())
return false;
partial.append(result);
}
if (partial.isEmpty())
builder.append("[]");
else {
if (m_gap.isEmpty()) {
builder.append('[');
for (unsigned i = 0; i < partial.size(); i++)
builder.append(partial[i]);
builder.append(']');
} else {
UString separator(",\n");
separator.append(m_indent);
builder.append("[\n");
builder.append(m_indent);
for (unsigned i = 0; i < partial.size(); i++) {
builder.append(partial[i]);
if (i < partial.size() - 1)
builder.append(separator);
}
builder.append('\n');
builder.append(stepBack);
builder.append(']');
}
}
m_indent = stepBack;
m_objectStack.remove(array);
return true;
}
bool stringifyObject(StringBuilder& builder, JSObject* object)
{
if (m_objectStack.contains(object)) {
throwError(m_exec, TypeError);
return false;
}
if (m_objectStack.size() > MaxObjectDepth) {
m_exec->setException(createStackOverflowError(m_exec));
return false;
}
m_objectStack.add(object);
UString stepBack = m_indent;
m_indent.append(m_gap);
Vector<StringBuilder, 16> partial;
PropertyNameArray objectPropertyNames(m_exec);
PropertyNameArray& sourcePropertyNames(m_arrayReplacerPropertyNames);
if (!m_usingArrayReplacer) {
object->getPropertyNames(m_exec, objectPropertyNames);
sourcePropertyNames = objectPropertyNames;
}
PropertyNameArray::const_iterator propEnd = sourcePropertyNames.end();
for (PropertyNameArray::const_iterator propIter = sourcePropertyNames.begin(); propIter != propEnd; propIter++) {
if (!object->hasOwnProperty(m_exec, *propIter))
continue;
JSValue value = object->get(m_exec, *propIter);
if (m_exec->hadException())
return false;
if (value.isUndefined())
continue;
StringBuilder result;
appendString(result,propIter->ustring());
result.append(':');
if (!m_gap.isEmpty())
result.append(' ');
StringKeyGenerator key(*propIter);
value = toJSONValue(key, value);
if (m_exec->hadException())
return false;
if (value.isUndefined())