michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "json.h" michael@0: michael@0: #include "mozilla/FloatingPoint.h" michael@0: michael@0: #include "jsarray.h" michael@0: #include "jsatom.h" michael@0: #include "jscntxt.h" michael@0: #include "jsnum.h" michael@0: #include "jsobj.h" michael@0: #include "jsonparser.h" michael@0: #include "jsstr.h" michael@0: #include "jstypes.h" michael@0: #include "jsutil.h" michael@0: michael@0: #include "vm/Interpreter.h" michael@0: #include "vm/StringBuffer.h" michael@0: michael@0: #include "jsatominlines.h" michael@0: #include "jsboolinlines.h" michael@0: #include "jsobjinlines.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::gc; michael@0: using namespace js::types; michael@0: michael@0: using mozilla::IsFinite; michael@0: using mozilla::Maybe; michael@0: michael@0: const Class js::JSONClass = { michael@0: js_JSON_str, michael@0: JSCLASS_HAS_CACHED_PROTO(JSProto_JSON), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub michael@0: }; michael@0: michael@0: static inline bool IsQuoteSpecialCharacter(jschar c) michael@0: { michael@0: JS_STATIC_ASSERT('\b' < ' '); michael@0: JS_STATIC_ASSERT('\f' < ' '); michael@0: JS_STATIC_ASSERT('\n' < ' '); michael@0: JS_STATIC_ASSERT('\r' < ' '); michael@0: JS_STATIC_ASSERT('\t' < ' '); michael@0: return c == '"' || c == '\\' || c < ' '; michael@0: } michael@0: michael@0: /* ES5 15.12.3 Quote. */ michael@0: static bool michael@0: Quote(JSContext *cx, StringBuffer &sb, JSString *str) michael@0: { michael@0: JS::Anchor anchor(str); michael@0: size_t len = str->length(); michael@0: const jschar *buf = str->getChars(cx); michael@0: if (!buf) michael@0: return false; michael@0: michael@0: /* Step 1. */ michael@0: if (!sb.append('"')) michael@0: return false; michael@0: michael@0: /* Step 2. */ michael@0: for (size_t i = 0; i < len; ++i) { michael@0: /* Batch-append maximal character sequences containing no escapes. */ michael@0: size_t mark = i; michael@0: do { michael@0: if (IsQuoteSpecialCharacter(buf[i])) michael@0: break; michael@0: } while (++i < len); michael@0: if (i > mark) { michael@0: if (!sb.append(&buf[mark], i - mark)) michael@0: return false; michael@0: if (i == len) michael@0: break; michael@0: } michael@0: michael@0: jschar c = buf[i]; michael@0: if (c == '"' || c == '\\') { michael@0: if (!sb.append('\\') || !sb.append(c)) michael@0: return false; michael@0: } else if (c == '\b' || c == '\f' || c == '\n' || c == '\r' || c == '\t') { michael@0: jschar abbrev = (c == '\b') michael@0: ? 'b' michael@0: : (c == '\f') michael@0: ? 'f' michael@0: : (c == '\n') michael@0: ? 'n' michael@0: : (c == '\r') michael@0: ? 'r' michael@0: : 't'; michael@0: if (!sb.append('\\') || !sb.append(abbrev)) michael@0: return false; michael@0: } else { michael@0: JS_ASSERT(c < ' '); michael@0: if (!sb.append("\\u00")) michael@0: return false; michael@0: JS_ASSERT((c >> 4) < 10); michael@0: uint8_t x = c >> 4, y = c % 16; michael@0: if (!sb.append('0' + x) || !sb.append(y < 10 ? '0' + y : 'a' + (y - 10))) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /* Steps 3-4. */ michael@0: return sb.append('"'); michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: class StringifyContext michael@0: { michael@0: public: michael@0: StringifyContext(JSContext *cx, StringBuffer &sb, const StringBuffer &gap, michael@0: HandleObject replacer, const AutoIdVector &propertyList) michael@0: : sb(sb), michael@0: gap(gap), michael@0: replacer(cx, replacer), michael@0: propertyList(propertyList), michael@0: depth(0) michael@0: {} michael@0: michael@0: StringBuffer &sb; michael@0: const StringBuffer ⪆ michael@0: RootedObject replacer; michael@0: const AutoIdVector &propertyList; michael@0: uint32_t depth; michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: static bool Str(JSContext *cx, const Value &v, StringifyContext *scx); michael@0: michael@0: static bool michael@0: WriteIndent(JSContext *cx, StringifyContext *scx, uint32_t limit) michael@0: { michael@0: if (!scx->gap.empty()) { michael@0: if (!scx->sb.append('\n')) michael@0: return false; michael@0: for (uint32_t i = 0; i < limit; i++) { michael@0: if (!scx->sb.append(scx->gap.begin(), scx->gap.end())) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: template michael@0: class KeyStringifier { michael@0: }; michael@0: michael@0: template<> michael@0: class KeyStringifier { michael@0: public: michael@0: static JSString *toString(JSContext *cx, uint32_t index) { michael@0: return IndexToString(cx, index); michael@0: } michael@0: }; michael@0: michael@0: template<> michael@0: class KeyStringifier { michael@0: public: michael@0: static JSString *toString(JSContext *cx, HandleId id) { michael@0: return IdToString(cx, id); michael@0: } michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: /* michael@0: * ES5 15.12.3 Str, steps 2-4, extracted to enable preprocessing of property michael@0: * values when stringifying objects in JO. michael@0: */ michael@0: template michael@0: static bool michael@0: PreprocessValue(JSContext *cx, HandleObject holder, KeyType key, MutableHandleValue vp, StringifyContext *scx) michael@0: { michael@0: RootedString keyStr(cx); michael@0: michael@0: /* Step 2. */ michael@0: if (vp.isObject()) { michael@0: RootedValue toJSON(cx); michael@0: RootedObject obj(cx, &vp.toObject()); michael@0: if (!JSObject::getProperty(cx, obj, obj, cx->names().toJSON, &toJSON)) michael@0: return false; michael@0: michael@0: if (js_IsCallable(toJSON)) { michael@0: keyStr = KeyStringifier::toString(cx, key); michael@0: if (!keyStr) michael@0: return false; michael@0: michael@0: InvokeArgs args(cx); michael@0: if (!args.init(1)) michael@0: return false; michael@0: michael@0: args.setCallee(toJSON); michael@0: args.setThis(vp); michael@0: args[0].setString(keyStr); michael@0: michael@0: if (!Invoke(cx, args)) michael@0: return false; michael@0: vp.set(args.rval()); michael@0: } michael@0: } michael@0: michael@0: /* Step 3. */ michael@0: if (scx->replacer && scx->replacer->isCallable()) { michael@0: if (!keyStr) { michael@0: keyStr = KeyStringifier::toString(cx, key); michael@0: if (!keyStr) michael@0: return false; michael@0: } michael@0: michael@0: InvokeArgs args(cx); michael@0: if (!args.init(2)) michael@0: return false; michael@0: michael@0: args.setCallee(ObjectValue(*scx->replacer)); michael@0: args.setThis(ObjectValue(*holder)); michael@0: args[0].setString(keyStr); michael@0: args[1].set(vp); michael@0: michael@0: if (!Invoke(cx, args)) michael@0: return false; michael@0: vp.set(args.rval()); michael@0: } michael@0: michael@0: /* Step 4. */ michael@0: if (vp.get().isObject()) { michael@0: RootedObject obj(cx, &vp.get().toObject()); michael@0: if (ObjectClassIs(obj, ESClass_Number, cx)) { michael@0: double d; michael@0: if (!ToNumber(cx, vp, &d)) michael@0: return false; michael@0: vp.set(NumberValue(d)); michael@0: } else if (ObjectClassIs(obj, ESClass_String, cx)) { michael@0: JSString *str = ToStringSlow(cx, vp); michael@0: if (!str) michael@0: return false; michael@0: vp.set(StringValue(str)); michael@0: } else if (ObjectClassIs(obj, ESClass_Boolean, cx)) { michael@0: vp.setBoolean(BooleanGetPrimitiveValue(obj)); michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Determines whether a value which has passed by ES5 150.2.3 Str steps 1-4's michael@0: * gauntlet will result in Str returning |undefined|. This function is used to michael@0: * properly omit properties resulting in such values when stringifying objects, michael@0: * while properly stringifying such properties as null when they're encountered michael@0: * in arrays. michael@0: */ michael@0: static inline bool michael@0: IsFilteredValue(const Value &v) michael@0: { michael@0: return v.isUndefined() || js_IsCallable(v); michael@0: } michael@0: michael@0: /* ES5 15.12.3 JO. */ michael@0: static bool michael@0: JO(JSContext *cx, HandleObject obj, StringifyContext *scx) michael@0: { michael@0: /* michael@0: * This method implements the JO algorithm in ES5 15.12.3, but: michael@0: * michael@0: * * The algorithm is somewhat reformulated to allow the final string to michael@0: * be streamed into a single buffer, rather than be created and copied michael@0: * into place incrementally as the ES5 algorithm specifies it. This michael@0: * requires moving portions of the Str call in 8a into this algorithm michael@0: * (and in JA as well). michael@0: */ michael@0: michael@0: /* Steps 1-2, 11. */ michael@0: AutoCycleDetector detect(cx, obj); michael@0: if (!detect.init()) michael@0: return false; michael@0: if (detect.foundCycle()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CYCLIC_VALUE, js_object_str); michael@0: return false; michael@0: } michael@0: michael@0: if (!scx->sb.append('{')) michael@0: return false; michael@0: michael@0: /* Steps 5-7. */ michael@0: Maybe ids; michael@0: const AutoIdVector *props; michael@0: if (scx->replacer && !scx->replacer->isCallable()) { michael@0: JS_ASSERT(JS_IsArrayObject(cx, scx->replacer)); michael@0: props = &scx->propertyList; michael@0: } else { michael@0: JS_ASSERT_IF(scx->replacer, scx->propertyList.length() == 0); michael@0: ids.construct(cx); michael@0: if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, ids.addr())) michael@0: return false; michael@0: props = ids.addr(); michael@0: } michael@0: michael@0: /* My kingdom for not-quite-initialized-from-the-start references. */ michael@0: const AutoIdVector &propertyList = *props; michael@0: michael@0: /* Steps 8-10, 13. */ michael@0: bool wroteMember = false; michael@0: RootedId id(cx); michael@0: for (size_t i = 0, len = propertyList.length(); i < len; i++) { michael@0: /* michael@0: * Steps 8a-8b. Note that the call to Str is broken up into 1) getting michael@0: * the property; 2) processing for toJSON, calling the replacer, and michael@0: * handling boxed Number/String/Boolean objects; 3) filtering out michael@0: * values which process to |undefined|, and 4) stringifying all values michael@0: * which pass the filter. michael@0: */ michael@0: id = propertyList[i]; michael@0: RootedValue outputValue(cx); michael@0: if (!JSObject::getGeneric(cx, obj, obj, id, &outputValue)) michael@0: return false; michael@0: if (!PreprocessValue(cx, obj, HandleId(id), &outputValue, scx)) michael@0: return false; michael@0: if (IsFilteredValue(outputValue)) michael@0: continue; michael@0: michael@0: /* Output a comma unless this is the first member to write. */ michael@0: if (wroteMember && !scx->sb.append(',')) michael@0: return false; michael@0: wroteMember = true; michael@0: michael@0: if (!WriteIndent(cx, scx, scx->depth)) michael@0: return false; michael@0: michael@0: JSString *s = IdToString(cx, id); michael@0: if (!s) michael@0: return false; michael@0: michael@0: if (!Quote(cx, scx->sb, s) || michael@0: !scx->sb.append(':') || michael@0: !(scx->gap.empty() || scx->sb.append(' ')) || michael@0: !Str(cx, outputValue, scx)) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (wroteMember && !WriteIndent(cx, scx, scx->depth - 1)) michael@0: return false; michael@0: michael@0: return scx->sb.append('}'); michael@0: } michael@0: michael@0: /* ES5 15.12.3 JA. */ michael@0: static bool michael@0: JA(JSContext *cx, HandleObject obj, StringifyContext *scx) michael@0: { michael@0: /* michael@0: * This method implements the JA algorithm in ES5 15.12.3, but: michael@0: * michael@0: * * The algorithm is somewhat reformulated to allow the final string to michael@0: * be streamed into a single buffer, rather than be created and copied michael@0: * into place incrementally as the ES5 algorithm specifies it. This michael@0: * requires moving portions of the Str call in 8a into this algorithm michael@0: * (and in JO as well). michael@0: */ michael@0: michael@0: /* Steps 1-2, 11. */ michael@0: AutoCycleDetector detect(cx, obj); michael@0: if (!detect.init()) michael@0: return false; michael@0: if (detect.foundCycle()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CYCLIC_VALUE, js_object_str); michael@0: return false; michael@0: } michael@0: michael@0: if (!scx->sb.append('[')) michael@0: return false; michael@0: michael@0: /* Step 6. */ michael@0: uint32_t length; michael@0: if (!GetLengthProperty(cx, obj, &length)) michael@0: return false; michael@0: michael@0: /* Steps 7-10. */ michael@0: if (length != 0) { michael@0: /* Steps 4, 10b(i). */ michael@0: if (!WriteIndent(cx, scx, scx->depth)) michael@0: return false; michael@0: michael@0: /* Steps 7-10. */ michael@0: RootedValue outputValue(cx); michael@0: for (uint32_t i = 0; i < length; i++) { michael@0: /* michael@0: * Steps 8a-8c. Again note how the call to the spec's Str method michael@0: * is broken up into getting the property, running it past toJSON michael@0: * and the replacer and maybe unboxing, and interpreting some michael@0: * values as |null| in separate steps. michael@0: */ michael@0: if (!JSObject::getElement(cx, obj, obj, i, &outputValue)) michael@0: return false; michael@0: if (!PreprocessValue(cx, obj, i, &outputValue, scx)) michael@0: return false; michael@0: if (IsFilteredValue(outputValue)) { michael@0: if (!scx->sb.append("null")) michael@0: return false; michael@0: } else { michael@0: if (!Str(cx, outputValue, scx)) michael@0: return false; michael@0: } michael@0: michael@0: /* Steps 3, 4, 10b(i). */ michael@0: if (i < length - 1) { michael@0: if (!scx->sb.append(',')) michael@0: return false; michael@0: if (!WriteIndent(cx, scx, scx->depth)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /* Step 10(b)(iii). */ michael@0: if (!WriteIndent(cx, scx, scx->depth - 1)) michael@0: return false; michael@0: } michael@0: michael@0: return scx->sb.append(']'); michael@0: } michael@0: michael@0: static bool michael@0: Str(JSContext *cx, const Value &v, StringifyContext *scx) michael@0: { michael@0: /* Step 11 must be handled by the caller. */ michael@0: JS_ASSERT(!IsFilteredValue(v)); michael@0: michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: michael@0: /* michael@0: * This method implements the Str algorithm in ES5 15.12.3, but: michael@0: * michael@0: * * We move property retrieval (step 1) into callers to stream the michael@0: * stringification process and avoid constantly copying strings. michael@0: * * We move the preprocessing in steps 2-4 into a helper function to michael@0: * allow both JO and JA to use this method. While JA could use it michael@0: * without this move, JO must omit any |undefined|-valued property per michael@0: * so it can't stream out a value using the Str method exactly as michael@0: * defined by ES5. michael@0: * * We move step 11 into callers, again to ease streaming. michael@0: */ michael@0: michael@0: /* Step 8. */ michael@0: if (v.isString()) michael@0: return Quote(cx, scx->sb, v.toString()); michael@0: michael@0: /* Step 5. */ michael@0: if (v.isNull()) michael@0: return scx->sb.append("null"); michael@0: michael@0: /* Steps 6-7. */ michael@0: if (v.isBoolean()) michael@0: return v.toBoolean() ? scx->sb.append("true") : scx->sb.append("false"); michael@0: michael@0: /* Step 9. */ michael@0: if (v.isNumber()) { michael@0: if (v.isDouble()) { michael@0: if (!IsFinite(v.toDouble())) michael@0: return scx->sb.append("null"); michael@0: } michael@0: michael@0: StringBuffer sb(cx); michael@0: if (!NumberValueToStringBuffer(cx, v, sb)) michael@0: return false; michael@0: michael@0: return scx->sb.append(sb.begin(), sb.length()); michael@0: } michael@0: michael@0: /* Step 10. */ michael@0: JS_ASSERT(v.isObject()); michael@0: RootedObject obj(cx, &v.toObject()); michael@0: michael@0: scx->depth++; michael@0: bool ok; michael@0: if (ObjectClassIs(obj, ESClass_Array, cx)) michael@0: ok = JA(cx, obj, scx); michael@0: else michael@0: ok = JO(cx, obj, scx); michael@0: scx->depth--; michael@0: michael@0: return ok; michael@0: } michael@0: michael@0: /* ES5 15.12.3. */ michael@0: bool michael@0: js_Stringify(JSContext *cx, MutableHandleValue vp, JSObject *replacer_, Value space_, michael@0: StringBuffer &sb) michael@0: { michael@0: RootedObject replacer(cx, replacer_); michael@0: RootedValue space(cx, space_); michael@0: michael@0: /* Step 4. */ michael@0: AutoIdVector propertyList(cx); michael@0: if (replacer) { michael@0: if (replacer->isCallable()) { michael@0: /* Step 4a(i): use replacer to transform values. */ michael@0: } else if (ObjectClassIs(replacer, ESClass_Array, cx)) { michael@0: /* michael@0: * Step 4b: The spec algorithm is unhelpfully vague about the exact michael@0: * steps taken when the replacer is an array, regarding the exact michael@0: * sequence of [[Get]] calls for the array's elements, when its michael@0: * overall length is calculated, whether own or own plus inherited michael@0: * properties are considered, and so on. A rewrite was proposed in michael@0: * , michael@0: * whose steps are copied below, and which are implemented here. michael@0: * michael@0: * i. Let PropertyList be an empty internal List. michael@0: * ii. Let len be the result of calling the [[Get]] internal michael@0: * method of replacer with the argument "length". michael@0: * iii. Let i be 0. michael@0: * iv. While i < len: michael@0: * 1. Let item be undefined. michael@0: * 2. Let v be the result of calling the [[Get]] internal michael@0: * method of replacer with the argument ToString(i). michael@0: * 3. If Type(v) is String then let item be v. michael@0: * 4. Else if Type(v) is Number then let item be ToString(v). michael@0: * 5. Else if Type(v) is Object then michael@0: * a. If the [[Class]] internal property of v is "String" michael@0: * or "Number" then let item be ToString(v). michael@0: * 6. If item is not undefined and item is not currently an michael@0: * element of PropertyList then, michael@0: * a. Append item to the end of PropertyList. michael@0: * 7. Let i be i + 1. michael@0: */ michael@0: michael@0: /* Step 4b(ii). */ michael@0: uint32_t len; michael@0: JS_ALWAYS_TRUE(GetLengthProperty(cx, replacer, &len)); michael@0: if (replacer->is() && !replacer->isIndexed()) michael@0: len = Min(len, replacer->getDenseInitializedLength()); michael@0: michael@0: // Cap the initial size to a moderately small value. This avoids michael@0: // ridiculous over-allocation if an array with bogusly-huge length michael@0: // is passed in. If we end up having to add elements past this michael@0: // size, the set will naturally resize to accommodate them. michael@0: const uint32_t MaxInitialSize = 1024; michael@0: HashSet idSet(cx); michael@0: if (!idSet.init(Min(len, MaxInitialSize))) michael@0: return false; michael@0: michael@0: /* Step 4b(iii). */ michael@0: uint32_t i = 0; michael@0: michael@0: /* Step 4b(iv). */ michael@0: RootedValue v(cx); michael@0: for (; i < len; i++) { michael@0: if (!CheckForInterrupt(cx)) michael@0: return false; michael@0: michael@0: /* Step 4b(iv)(2). */ michael@0: if (!JSObject::getElement(cx, replacer, replacer, i, &v)) michael@0: return false; michael@0: michael@0: RootedId id(cx); michael@0: if (v.isNumber()) { michael@0: /* Step 4b(iv)(4). */ michael@0: int32_t n; michael@0: if (v.isNumber() && ValueFitsInInt32(v, &n) && INT_FITS_IN_JSID(n)) { michael@0: id = INT_TO_JSID(n); michael@0: } else { michael@0: if (!ValueToId(cx, v, &id)) michael@0: return false; michael@0: } michael@0: } else if (v.isString() || michael@0: IsObjectWithClass(v, ESClass_String, cx) || michael@0: IsObjectWithClass(v, ESClass_Number, cx)) michael@0: { michael@0: /* Step 4b(iv)(3), 4b(iv)(5). */ michael@0: if (!ValueToId(cx, v, &id)) michael@0: return false; michael@0: } else { michael@0: continue; michael@0: } michael@0: michael@0: /* Step 4b(iv)(6). */ michael@0: HashSet::AddPtr p = idSet.lookupForAdd(id); michael@0: if (!p) { michael@0: /* Step 4b(iv)(6)(a). */ michael@0: if (!idSet.add(p, id) || !propertyList.append(id)) michael@0: return false; michael@0: } michael@0: } michael@0: } else { michael@0: replacer = nullptr; michael@0: } michael@0: } michael@0: michael@0: /* Step 5. */ michael@0: if (space.isObject()) { michael@0: RootedObject spaceObj(cx, &space.toObject()); michael@0: if (ObjectClassIs(spaceObj, ESClass_Number, cx)) { michael@0: double d; michael@0: if (!ToNumber(cx, space, &d)) michael@0: return false; michael@0: space = NumberValue(d); michael@0: } else if (ObjectClassIs(spaceObj, ESClass_String, cx)) { michael@0: JSString *str = ToStringSlow(cx, space); michael@0: if (!str) michael@0: return false; michael@0: space = StringValue(str); michael@0: } michael@0: } michael@0: michael@0: StringBuffer gap(cx); michael@0: michael@0: if (space.isNumber()) { michael@0: /* Step 6. */ michael@0: double d; michael@0: JS_ALWAYS_TRUE(ToInteger(cx, space, &d)); michael@0: d = Min(10.0, d); michael@0: if (d >= 1 && !gap.appendN(' ', uint32_t(d))) michael@0: return false; michael@0: } else if (space.isString()) { michael@0: /* Step 7. */ michael@0: JSLinearString *str = space.toString()->ensureLinear(cx); michael@0: if (!str) michael@0: return false; michael@0: JS::Anchor anchor(str); michael@0: size_t len = Min(size_t(10), space.toString()->length()); michael@0: if (!gap.append(str->chars(), len)) michael@0: return false; michael@0: } else { michael@0: /* Step 8. */ michael@0: JS_ASSERT(gap.empty()); michael@0: } michael@0: michael@0: /* Step 9. */ michael@0: RootedObject wrapper(cx, NewBuiltinClassInstance(cx, &JSObject::class_)); michael@0: if (!wrapper) michael@0: return false; michael@0: michael@0: /* Step 10. */ michael@0: RootedId emptyId(cx, NameToId(cx->names().empty)); michael@0: if (!DefineNativeProperty(cx, wrapper, emptyId, vp, JS_PropertyStub, JS_StrictPropertyStub, michael@0: JSPROP_ENUMERATE)) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: /* Step 11. */ michael@0: StringifyContext scx(cx, sb, gap, replacer, propertyList); michael@0: if (!PreprocessValue(cx, wrapper, HandleId(emptyId), vp, &scx)) michael@0: return false; michael@0: if (IsFilteredValue(vp)) michael@0: return true; michael@0: michael@0: return Str(cx, vp, &scx); michael@0: } michael@0: michael@0: /* ES5 15.12.2 Walk. */ michael@0: static bool michael@0: Walk(JSContext *cx, HandleObject holder, HandleId name, HandleValue reviver, MutableHandleValue vp) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: michael@0: /* Step 1. */ michael@0: RootedValue val(cx); michael@0: if (!JSObject::getGeneric(cx, holder, holder, name, &val)) michael@0: return false; michael@0: michael@0: /* Step 2. */ michael@0: if (val.isObject()) { michael@0: RootedObject obj(cx, &val.toObject()); michael@0: michael@0: if (ObjectClassIs(obj, ESClass_Array, cx)) { michael@0: /* Step 2a(ii). */ michael@0: uint32_t length; michael@0: if (!GetLengthProperty(cx, obj, &length)) michael@0: return false; michael@0: michael@0: /* Step 2a(i), 2a(iii-iv). */ michael@0: RootedId id(cx); michael@0: RootedValue newElement(cx); michael@0: for (uint32_t i = 0; i < length; i++) { michael@0: if (!IndexToId(cx, i, &id)) michael@0: return false; michael@0: michael@0: /* Step 2a(iii)(1). */ michael@0: if (!Walk(cx, obj, id, reviver, &newElement)) michael@0: return false; michael@0: michael@0: if (newElement.isUndefined()) { michael@0: /* Step 2a(iii)(2). */ michael@0: bool succeeded; michael@0: if (!JSObject::deleteByValue(cx, obj, IdToValue(id), &succeeded)) michael@0: return false; michael@0: } else { michael@0: /* Step 2a(iii)(3). */ michael@0: // XXX This definition should ignore success/failure, when michael@0: // our property-definition APIs indicate that. michael@0: if (!JSObject::defineGeneric(cx, obj, id, newElement)) michael@0: return false; michael@0: } michael@0: } michael@0: } else { michael@0: /* Step 2b(i). */ michael@0: AutoIdVector keys(cx); michael@0: if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, &keys)) michael@0: return false; michael@0: michael@0: /* Step 2b(ii). */ michael@0: RootedId id(cx); michael@0: RootedValue newElement(cx); michael@0: for (size_t i = 0, len = keys.length(); i < len; i++) { michael@0: /* Step 2b(ii)(1). */ michael@0: id = keys[i]; michael@0: if (!Walk(cx, obj, id, reviver, &newElement)) michael@0: return false; michael@0: michael@0: if (newElement.isUndefined()) { michael@0: /* Step 2b(ii)(2). */ michael@0: bool succeeded; michael@0: if (!JSObject::deleteByValue(cx, obj, IdToValue(id), &succeeded)) michael@0: return false; michael@0: } else { michael@0: /* Step 2b(ii)(3). */ michael@0: // XXX This definition should ignore success/failure, when michael@0: // our property-definition APIs indicate that. michael@0: if (!JSObject::defineGeneric(cx, obj, id, newElement)) michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* Step 3. */ michael@0: RootedString key(cx, IdToString(cx, name)); michael@0: if (!key) michael@0: return false; michael@0: michael@0: InvokeArgs args(cx); michael@0: if (!args.init(2)) michael@0: return false; michael@0: michael@0: args.setCallee(reviver); michael@0: args.setThis(ObjectValue(*holder)); michael@0: args[0].setString(key); michael@0: args[1].set(val); michael@0: michael@0: if (!Invoke(cx, args)) michael@0: return false; michael@0: vp.set(args.rval()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: Revive(JSContext *cx, HandleValue reviver, MutableHandleValue vp) michael@0: { michael@0: RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: if (!JSObject::defineProperty(cx, obj, cx->names().empty, vp)) michael@0: return false; michael@0: michael@0: Rooted id(cx, NameToId(cx->names().empty)); michael@0: return Walk(cx, obj, id, reviver, vp); michael@0: } michael@0: michael@0: bool michael@0: js::ParseJSONWithReviver(JSContext *cx, ConstTwoByteChars chars, size_t length, michael@0: HandleValue reviver, MutableHandleValue vp) michael@0: { michael@0: /* 15.12.2 steps 2-3. */ michael@0: JSONParser parser(cx, chars, length); michael@0: if (!parser.parse(vp)) michael@0: return false; michael@0: michael@0: /* 15.12.2 steps 4-5. */ michael@0: if (js_IsCallable(reviver)) michael@0: return Revive(cx, reviver, vp); michael@0: return true; michael@0: } michael@0: michael@0: #if JS_HAS_TOSOURCE michael@0: static bool michael@0: json_toSource(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: args.rval().setString(cx->names().JSON); michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: /* ES5 15.12.2. */ michael@0: static bool michael@0: json_parse(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: /* Step 1. */ michael@0: JSString *str = (args.length() >= 1) michael@0: ? ToString(cx, args[0]) michael@0: : cx->names().undefined; michael@0: if (!str) michael@0: return false; michael@0: michael@0: Rooted flat(cx, str->ensureFlat(cx)); michael@0: if (!flat) michael@0: return false; michael@0: michael@0: JS::Anchor anchor(flat); michael@0: michael@0: RootedValue reviver(cx, args.get(1)); michael@0: michael@0: /* Steps 2-5. */ michael@0: return ParseJSONWithReviver(cx, ConstTwoByteChars(flat->chars(), flat->length()), michael@0: flat->length(), reviver, args.rval()); michael@0: } michael@0: michael@0: /* ES5 15.12.3. */ michael@0: bool michael@0: json_stringify(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject replacer(cx, args.get(1).isObject() ? &args[1].toObject() : nullptr); michael@0: RootedValue value(cx, args.get(0)); michael@0: RootedValue space(cx, args.get(2)); michael@0: michael@0: StringBuffer sb(cx); michael@0: if (!js_Stringify(cx, &value, replacer, space, sb)) michael@0: return false; michael@0: michael@0: // XXX This can never happen to nsJSON.cpp, but the JSON object michael@0: // needs to support returning undefined. So this is a little awkward michael@0: // for the API, because we want to support streaming writers. michael@0: if (!sb.empty()) { michael@0: JSString *str = sb.finishString(); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: } else { michael@0: args.rval().setUndefined(); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static const JSFunctionSpec json_static_methods[] = { michael@0: #if JS_HAS_TOSOURCE michael@0: JS_FN(js_toSource_str, json_toSource, 0, 0), michael@0: #endif michael@0: JS_FN("parse", json_parse, 2, 0), michael@0: JS_FN("stringify", json_stringify, 3, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: JSObject * michael@0: js_InitJSONClass(JSContext *cx, HandleObject obj) michael@0: { michael@0: Rooted global(cx, &obj->as()); michael@0: michael@0: /* michael@0: * JSON requires that Boolean.prototype.valueOf be created and stashed in a michael@0: * reserved slot on the global object; see js::BooleanGetPrimitiveValueSlow michael@0: * called from PreprocessValue above. michael@0: */ michael@0: if (!GlobalObject::getOrCreateBooleanPrototype(cx, global)) michael@0: return nullptr; michael@0: michael@0: RootedObject proto(cx, obj->as().getOrCreateObjectPrototype(cx)); michael@0: RootedObject JSON(cx, NewObjectWithClassProto(cx, &JSONClass, proto, global, SingletonObject)); michael@0: if (!JSON) michael@0: return nullptr; michael@0: michael@0: if (!JS_DefineProperty(cx, global, js_JSON_str, JSON, 0, michael@0: JS_PropertyStub, JS_StrictPropertyStub)) michael@0: return nullptr; michael@0: michael@0: if (!JS_DefineFunctions(cx, JSON, json_static_methods)) michael@0: return nullptr; michael@0: michael@0: global->setConstructor(JSProto_JSON, ObjectValue(*JSON)); michael@0: michael@0: return JSON; michael@0: }