#include "JSArray.h"
#include "LiteralParser.h"
#include "PropertyNameArray.h"
+#include "StringBuilder.h"
#include <wtf/MathExtras.h>
namespace JSC {
mutable JSValue m_value;
};
-class Stringifier : Noncopyable {
+class Stringifier : public Noncopyable {
public:
Stringifier(ExecState*, JSValue replacer, JSValue space);
~Stringifier();
JSValue stringify(JSValue);
- void mark();
+ void markAggregate(MarkStack&);
private:
- typedef UString StringBuilder;
-
class Holder {
public:
Holder(JSObject*);
// ------------------------------ helper functions --------------------------------
-static inline JSValue unwrapBoxedPrimitive(JSValue value)
+static inline JSValue unwrapBoxedPrimitive(ExecState* exec, JSValue value)
{
if (!value.isObject())
return value;
- if (!asObject(value)->inherits(&NumberObject::info) && !asObject(value)->inherits(&StringObject::info) && !asObject(value)->inherits(&BooleanObject::info))
- return value;
- return static_cast<JSWrapperObject*>(asObject(value))->internalValue();
+ JSObject* object = asObject(value);
+ if (object->inherits(&NumberObject::info))
+ return jsNumber(exec, object->toNumber(exec));
+ if (object->inherits(&StringObject::info))
+ return jsString(exec, object->toString(exec));
+ if (object->inherits(&BooleanObject::info))
+ return object->toPrimitive(exec);
+ return value;
}
-static inline UString gap(JSValue space)
+static inline UString gap(ExecState* exec, JSValue space)
{
- space = unwrapBoxedPrimitive(space);
+ const int maxGapLength = 10;
+ space = unwrapBoxedPrimitive(exec, space);
// If the space value is a number, create a gap string with that number of spaces.
double spaceCount;
if (space.getNumber(spaceCount)) {
- const int maxSpaceCount = 100;
int count;
- if (spaceCount > maxSpaceCount)
- count = maxSpaceCount;
+ if (spaceCount > maxGapLength)
+ count = maxGapLength;
else if (!(spaceCount > 0))
count = 0;
else
count = static_cast<int>(spaceCount);
- UChar spaces[maxSpaceCount];
+ UChar spaces[maxGapLength];
for (int i = 0; i < count; ++i)
spaces[i] = ' ';
return UString(spaces, count);
}
// If the space value is a string, use it as the gap string, otherwise use no gap string.
- return space.getString();
+ UString spaces = space.getString(exec);
+ if (spaces.size() > maxGapLength) {
+ spaces = spaces.substr(0, maxGapLength);
+ }
+ return spaces;
}
// ------------------------------ PropertyNameForFunctionCall --------------------------------
, m_usingArrayReplacer(false)
, m_arrayReplacerPropertyNames(exec)
, m_replacerCallType(CallTypeNone)
- , m_gap(gap(space))
+ , m_gap(gap(exec, space))
{
exec->globalData().firstStringifierToMark = this;
JSValue name = array->get(exec, i);
if (exec->hadException())
break;
+
UString propertyName;
- if (!name.getString(propertyName))
+ if (name.getString(exec, propertyName)) {
+ m_arrayReplacerPropertyNames.add(Identifier(exec, propertyName));
continue;
- if (exec->hadException())
- return;
- m_arrayReplacerPropertyNames.add(Identifier(exec, propertyName));
+ }
+
+ double value = 0;
+ if (name.getNumber(value)) {
+ m_arrayReplacerPropertyNames.add(Identifier::from(exec, value));
+ continue;
+ }
+
+ if (name.isObject()) {
+ if (!asObject(name)->inherits(&NumberObject::info) && !asObject(name)->inherits(&StringObject::info))
+ continue;
+ propertyName = name.toString(exec);
+ if (exec->hadException())
+ break;
+ m_arrayReplacerPropertyNames.add(Identifier(exec, propertyName));
+ }
}
return;
}
m_exec->globalData().firstStringifierToMark = m_nextStringifierToMark;
}
-void Stringifier::mark()
+void Stringifier::markAggregate(MarkStack& markStack)
{
for (Stringifier* stringifier = this; stringifier; stringifier = stringifier->m_nextStringifierToMark) {
size_t size = m_holderStack.size();
- for (size_t i = 0; i < size; ++i) {
- JSObject* object = m_holderStack[i].object();
- if (!object->marked())
- object->mark();
- }
+ for (size_t i = 0; i < size; ++i)
+ markStack.append(m_holderStack[i].object());
}
}
if (m_exec->hadException())
return jsNull();
- return jsString(m_exec, result);
+ return jsString(m_exec, result.release());
}
void Stringifier::appendQuotedString(StringBuilder& builder, const UString& value)
value = toJSON(value, propertyName);
if (m_exec->hadException())
return StringifyFailed;
- if (value.isUndefined() && !holder->inherits(&JSArray::info))
- return StringifyFailedDueToUndefinedValue;
// Call the replacer function.
if (m_replacerCallType != CallTypeNone) {
return StringifyFailed;
}
+ if (value.isUndefined() && !holder->inherits(&JSArray::info))
+ return StringifyFailedDueToUndefinedValue;
+
if (value.isNull()) {
builder.append("null");
return StringifySucceeded;
}
- value = unwrapBoxedPrimitive(value);
+ value = unwrapBoxedPrimitive(m_exec, value);
+
+ if (m_exec->hadException())
+ return StringifyFailed;
if (value.isBoolean()) {
builder.append(value.getBoolean() ? "true" : "false");
}
UString stringValue;
- if (value.getString(stringValue)) {
+ if (value.getString(m_exec, stringValue)) {
appendQuotedString(builder, stringValue);
return StringifySucceeded;
}
JSObject* object = asObject(value);
+ CallData callData;
+ if (object->getCallData(callData) != CallTypeNone) {
+ if (holder->inherits(&JSArray::info)) {
+ builder.append("null");
+ return StringifySucceeded;
+ }
+ return StringifyFailedDueToUndefinedValue;
+ }
+
// Handle cycle detection, and put the holder on the stack.
if (!m_holderCycleDetector.add(object).second) {
- throwError(m_exec, TypeError);
+ throwError(m_exec, TypeError, "JSON.stringify cannot serialize cyclic structures.");
return StringifyFailed;
}
bool holderStackWasEmpty = m_holderStack.isEmpty();
// Use a single shared string, m_repeatedGap, so we don't keep allocating new ones as we indent and unindent.
int newSize = m_indent.size() + m_gap.size();
if (newSize > m_repeatedGap.size())
- m_repeatedGap.append(m_gap);
+ m_repeatedGap = makeString(m_repeatedGap, m_gap);
ASSERT(newSize <= m_repeatedGap.size());
m_indent = m_repeatedGap.substr(0, newSize);
}
m_propertyNames = stringifier.m_arrayReplacerPropertyNames.data();
else {
PropertyNameArray objectPropertyNames(exec);
- m_object->getPropertyNames(exec, objectPropertyNames);
+ m_object->getOwnPropertyNames(exec, objectPropertyNames);
m_propertyNames = objectPropertyNames.releaseData();
}
m_size = m_propertyNames->propertyNameVector().size();
// This only occurs when get an undefined value for an object property.
// In this case we don't want the separator and property name that we
// already appended, so roll back.
- builder = builder.substr(0, rollBackPoint);
+ builder.resize(rollBackPoint);
break;
}
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);
+ return getStaticFunctionSlot<JSObject>(exec, ExecState::jsonTable(exec), this, propertyName, slot);
+}
- ASSERT(entry->attributes() & Function);
- setUpStaticFunctionSlot(exec, entry, this, propertyName, slot);
- return true;
+bool JSONObject::getOwnPropertyDescriptor(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor)
+{
+ return getStaticFunctionDescriptor<JSObject>(exec, ExecState::jsonTable(exec), this, propertyName, descriptor);
}
-void JSONObject::markStringifiers(Stringifier* stringifier)
+void JSONObject::markStringifiers(MarkStack& markStack, Stringifier* stringifier)
{
- stringifier->mark();
+ stringifier->markAggregate(markStack);
}
class Walker {
}
JSValue walk(JSValue unfiltered);
private:
- JSValue callReviver(JSValue property, JSValue unfiltered)
+ JSValue callReviver(JSObject* thisObj, JSValue property, JSValue unfiltered)
{
JSValue args[] = { property, unfiltered };
ArgList argList(args, 2);
- return call(m_exec, m_function, m_callType, m_callData, jsNull(), argList);
+ return call(m_exec, m_function, m_callType, m_callData, thisObj, argList);
}
friend class Holder;
CallType m_callType;
CallData m_callData;
};
-
+
+// We clamp recursion well beyond anything reasonable, but we also have a timeout check
+// to guard against "infinite" execution by inserting arbitrarily large objects.
+static const unsigned maximumFilterRecursion = 40000;
enum WalkerState { StateUnknown, ArrayStartState, ArrayStartVisitMember, ArrayEndVisitMember,
ObjectStartState, ObjectStartVisitMember, ObjectEndVisitMember };
NEVER_INLINE JSValue Walker::walk(JSValue unfiltered)
WalkerState state = StateUnknown;
JSValue inValue = unfiltered;
JSValue outValue = jsNull();
+
+ TimeoutChecker localTimeoutChecker(m_exec->globalData().timeoutChecker);
+ localTimeoutChecker.reset();
+ unsigned tickCount = localTimeoutChecker.ticksUntilNextCheck();
while (1) {
switch (state) {
arrayStartState:
case ArrayStartState: {
ASSERT(inValue.isObject());
- ASSERT(isJSArray(&m_exec->globalData(), asObject(inValue)));
+ ASSERT(isJSArray(&m_exec->globalData(), asObject(inValue)) || asObject(inValue)->inherits(&JSArray::info));
+ if (objectStack.size() + arrayStack.size() > maximumFilterRecursion) {
+ m_exec->setException(createStackOverflowError(m_exec));
+ return jsUndefined();
+ }
+
JSArray* array = asArray(inValue);
arrayStack.append(array);
indexStack.append(0);
}
arrayStartVisitMember:
case ArrayStartVisitMember: {
+ if (!--tickCount) {
+ if (localTimeoutChecker.didTimeOut(m_exec)) {
+ m_exec->setException(createInterruptedExecutionException(&m_exec->globalData()));
+ return jsUndefined();
+ }
+ tickCount = localTimeoutChecker.ticksUntilNextCheck();
+ }
+
JSArray* array = arrayStack.last();
uint32_t index = indexStack.last();
if (index == array->length()) {
indexStack.removeLast();
break;
}
- inValue = array->getIndex(index);
+ if (isJSArray(&m_exec->globalData(), array) && array->canGetIndex(index))
+ inValue = array->getIndex(index);
+ else {
+ PropertySlot slot;
+ if (array->getOwnPropertySlot(m_exec, index, slot))
+ inValue = slot.getValue(m_exec, index);
+ else
+ inValue = jsUndefined();
+ }
+
if (inValue.isObject()) {
stateStack.append(ArrayEndVisitMember);
goto stateUnknown;
}
case ArrayEndVisitMember: {
JSArray* array = arrayStack.last();
- array->setIndex(indexStack.last(), callReviver(jsString(m_exec, UString::from(indexStack.last())), outValue));
+ JSValue filteredValue = callReviver(array, jsString(m_exec, UString::from(indexStack.last())), outValue);
+ if (filteredValue.isUndefined())
+ array->deleteProperty(m_exec, indexStack.last());
+ else {
+ if (isJSArray(&m_exec->globalData(), array) && array->canSetIndex(indexStack.last()))
+ array->setIndex(indexStack.last(), filteredValue);
+ else
+ array->put(m_exec, indexStack.last(), filteredValue);
+ }
if (m_exec->hadException())
return jsNull();
indexStack.last()++;
objectStartState:
case ObjectStartState: {
ASSERT(inValue.isObject());
- ASSERT(!isJSArray(&m_exec->globalData(), asObject(inValue)));
+ ASSERT(!isJSArray(&m_exec->globalData(), asObject(inValue)) && !asObject(inValue)->inherits(&JSArray::info));
+ if (objectStack.size() + arrayStack.size() > maximumFilterRecursion) {
+ m_exec->setException(createStackOverflowError(m_exec));
+ return jsUndefined();
+ }
+
JSObject* object = asObject(inValue);
objectStack.append(object);
indexStack.append(0);
propertyStack.append(PropertyNameArray(m_exec));
- object->getPropertyNames(m_exec, propertyStack.last());
+ object->getOwnPropertyNames(m_exec, propertyStack.last());
// fallthrough
}
objectStartVisitMember:
case ObjectStartVisitMember: {
+ if (!--tickCount) {
+ if (localTimeoutChecker.didTimeOut(m_exec)) {
+ m_exec->setException(createInterruptedExecutionException(&m_exec->globalData()));
+ return jsUndefined();
+ }
+ tickCount = localTimeoutChecker.ticksUntilNextCheck();
+ }
+
JSObject* object = objectStack.last();
uint32_t index = indexStack.last();
PropertyNameArray& properties = propertyStack.last();
break;
}
PropertySlot slot;
- object->getOwnPropertySlot(m_exec, properties[index], slot);
- inValue = slot.getValue(m_exec, properties[index]);
- ASSERT(!m_exec->hadException());
+ if (object->getOwnPropertySlot(m_exec, properties[index], slot))
+ inValue = slot.getValue(m_exec, properties[index]);
+ else
+ inValue = jsUndefined();
+
+ // The holder may be modified by the reviver function so any lookup may throw
+ if (m_exec->hadException())
+ return jsNull();
+
if (inValue.isObject()) {
stateStack.append(ObjectEndVisitMember);
goto stateUnknown;
JSObject* object = objectStack.last();
Identifier prop = propertyStack.last()[indexStack.last()];
PutPropertySlot slot;
- object->put(m_exec, prop, callReviver(jsString(m_exec, prop.ustring()), outValue), slot);
+ JSValue filteredValue = callReviver(object, jsString(m_exec, prop.ustring()), outValue);
+ if (filteredValue.isUndefined())
+ object->deleteProperty(m_exec, prop);
+ else
+ object->put(m_exec, prop, filteredValue, slot);
if (m_exec->hadException())
return jsNull();
indexStack.last()++;
outValue = inValue;
break;
}
- if (isJSArray(&m_exec->globalData(), asObject(inValue)))
+ JSObject* object = asObject(inValue);
+ if (isJSArray(&m_exec->globalData(), object) || object->inherits(&JSArray::info))
goto arrayStartState;
goto objectStartState;
}
if (stateStack.isEmpty())
break;
+
state = stateStack.last();
stateStack.removeLast();
+
+ if (!--tickCount) {
+ if (localTimeoutChecker.didTimeOut(m_exec)) {
+ m_exec->setException(createInterruptedExecutionException(&m_exec->globalData()));
+ return jsUndefined();
+ }
+ tickCount = localTimeoutChecker.ticksUntilNextCheck();
+ }
}
- return callReviver(jsEmptyString(m_exec), outValue);
+ JSObject* finalHolder = constructEmptyObject(m_exec);
+ PutPropertySlot slot;
+ finalHolder->put(m_exec, m_exec->globalData().propertyNames->emptyIdentifier, outValue, slot);
+ return callReviver(finalHolder, jsEmptyString(m_exec), outValue);
}
// ECMA-262 v5 15.12.2