static bool CloneValue(JSContext *cx, MutableHandleValue vp, CloneMemory &clonedObjects) { if (vp.isObject()) { RootedObject obj(cx, &vp.toObject()); RootedObject clone(cx, CloneObject(cx, obj, clonedObjects)); if (!clone) return false; vp.setObject(*clone); } else if (vp.isBoolean() || vp.isNumber() || vp.isNullOrUndefined()) { // Nothing to do here: these are represented inline in the value } else if (vp.isString()) { Rooted<JSStableString*> str(cx, vp.toString()->ensureStable(cx)); if (!str) return false; RootedString clone(cx, js_NewStringCopyN<CanGC>(cx, str->chars().get(), str->length())); if (!clone) return false; vp.setString(clone); } else { if (JSString *valSrc = JS_ValueToSource(cx, vp)) printf("Error: Can't yet clone value: %s\n", JS_EncodeString(cx, valSrc)); return false; } return true; }
bool JSRuntime::maybeWrappedSelfHostedFunction(JSContext *cx, HandleId id, MutableHandleValue funVal) { RootedObject shg(cx, selfHostingGlobal_); if (!GetUnclonedValue(cx, shg, id, funVal)) return false; JS_ASSERT(funVal.isObject()); JS_ASSERT(funVal.toObject().isCallable()); if (!funVal.toObject().as<JSFunction>().isWrappable()) { funVal.setUndefined(); return true; } return cx->compartment()->wrap(cx, funVal); }
/* * General-purpose structured-cloning utility for cases where the structured * clone buffer is only used in stack-scope (that is to say, the buffer does * not escape from this function). The stack-scoping allows us to pass * references to various JSObjects directly in certain situations without * worrying about lifetime issues. * * This function assumes that |cx| is already entered the compartment we want * to clone to, and that |val| may not be same-compartment with cx. When the * function returns, |val| is set to the result of the clone. */ bool StackScopedClone(JSContext *cx, StackScopedCloneOptions &options, MutableHandleValue val) { JSAutoStructuredCloneBuffer buffer; StackScopedCloneData data(cx, &options); { // For parsing val we have to enter its compartment. // (unless it's a primitive) Maybe<JSAutoCompartment> ac; if (val.isObject()) { ac.construct(cx, &val.toObject()); } else if (val.isString() && !JS_WrapValue(cx, val)) { return false; } if (!buffer.write(cx, val, &gStackScopedCloneCallbacks, &data)) return false; } // Now recreate the clones in the target compartment. return buffer.read(cx, val, &gStackScopedCloneCallbacks, &data); }
static bool CloneValue(JSContext *cx, MutableHandleValue vp, CloneMemory &clonedObjects) { if (vp.isObject()) { RootedObject obj(cx, &vp.toObject()); RootedObject clone(cx, CloneObject(cx, obj, clonedObjects)); if (!clone) return false; vp.setObject(*clone); } else if (vp.isBoolean() || vp.isNumber() || vp.isNullOrUndefined()) { // Nothing to do here: these are represented inline in the value } else if (vp.isString()) { Rooted<JSStableString*> str(cx, vp.toString()->ensureStable(cx)); if (!str) return false; RootedString clone(cx, js_NewStringCopyN<CanGC>(cx, str->chars().get(), str->length())); if (!clone) return false; vp.setString(clone); } else { MOZ_ASSUME_UNREACHABLE("Self-hosting CloneValue can't clone given value."); } return true; }
static bool PreprocessValue(JSContext* cx, HandleObject holder, KeyType key, MutableHandleValue vp, StringifyContext* scx) { RootedString keyStr(cx); /* Step 2. */ if (vp.isObject()) { RootedValue toJSON(cx); RootedObject obj(cx, &vp.toObject()); if (!GetProperty(cx, obj, obj, cx->names().toJSON, &toJSON)) return false; if (IsCallable(toJSON)) { keyStr = KeyStringifier<KeyType>::toString(cx, key); if (!keyStr) return false; InvokeArgs args(cx); if (!args.init(1)) return false; args.setCallee(toJSON); args.setThis(vp); args[0].setString(keyStr); if (!Invoke(cx, args)) return false; vp.set(args.rval()); } } /* Step 3. */ if (scx->replacer && scx->replacer->isCallable()) { if (!keyStr) { keyStr = KeyStringifier<KeyType>::toString(cx, key); if (!keyStr) return false; } InvokeArgs args(cx); if (!args.init(2)) return false; args.setCallee(ObjectValue(*scx->replacer)); args.setThis(ObjectValue(*holder)); args[0].setString(keyStr); args[1].set(vp); if (!Invoke(cx, args)) return false; vp.set(args.rval()); } /* Step 4. */ if (vp.get().isObject()) { RootedObject obj(cx, &vp.get().toObject()); if (ObjectClassIs(obj, ESClass_Number, cx)) { double d; if (!ToNumber(cx, vp, &d)) return false; vp.setNumber(d); } else if (ObjectClassIs(obj, ESClass_String, cx)) { JSString* str = ToStringSlow<CanGC>(cx, vp); if (!str) return false; vp.setString(str); } else if (ObjectClassIs(obj, ESClass_Boolean, cx)) { if (!Unbox(cx, obj, vp)) return false; } } return true; }
bool JSCompartment::wrap(JSContext *cx, MutableHandleValue vp, HandleObject existingArg) { JS_ASSERT(cx->compartment == this); JS_ASSERT_IF(existingArg, existingArg->compartment() == cx->compartment); JS_ASSERT_IF(existingArg, vp.isObject()); JS_ASSERT_IF(existingArg, IsDeadProxyObject(existingArg)); unsigned flags = 0; JS_CHECK_CHROME_RECURSION(cx, return false); #ifdef DEBUG struct AutoDisableProxyCheck { JSRuntime *runtime; AutoDisableProxyCheck(JSRuntime *rt) : runtime(rt) { runtime->gcDisableStrictProxyCheckingCount++; } ~AutoDisableProxyCheck() { runtime->gcDisableStrictProxyCheckingCount--; } } adpc(rt); #endif /* Only GC things have to be wrapped or copied. */ if (!vp.isMarkable()) return true; if (vp.isString()) { RawString str = vp.toString(); /* If the string is already in this compartment, we are done. */ if (str->zone() == zone()) return true; /* If the string is an atom, we don't have to copy. */ if (str->isAtom()) { JS_ASSERT(str->zone() == cx->runtime->atomsCompartment->zone()); return true; } } /* * Wrappers should really be parented to the wrapped parent of the wrapped * object, but in that case a wrapped global object would have a NULL * parent without being a proper global object (JSCLASS_IS_GLOBAL). Instead, * we parent all wrappers to the global object in their home compartment. * This loses us some transparency, and is generally very cheesy. */ HandleObject global = cx->global(); /* Unwrap incoming objects. */ if (vp.isObject()) { RootedObject obj(cx, &vp.toObject()); if (obj->compartment() == this) return WrapForSameCompartment(cx, obj, vp); /* Translate StopIteration singleton. */ if (obj->isStopIteration()) return js_FindClassObject(cx, JSProto_StopIteration, vp); /* Unwrap the object, but don't unwrap outer windows. */ obj = UnwrapObject(obj, /* stopAtOuter = */ true, &flags); if (obj->compartment() == this) return WrapForSameCompartment(cx, obj, vp); if (cx->runtime->preWrapObjectCallback) { obj = cx->runtime->preWrapObjectCallback(cx, global, obj, flags); if (!obj) return false; } if (obj->compartment() == this) return WrapForSameCompartment(cx, obj, vp); vp.setObject(*obj); #ifdef DEBUG { JSObject *outer = GetOuterObject(cx, obj); JS_ASSERT(outer && outer == obj); } #endif } RootedValue key(cx, vp); /* If we already have a wrapper for this value, use it. */ if (WrapperMap::Ptr p = crossCompartmentWrappers.lookup(key)) { vp.set(p->value); if (vp.isObject()) { RawObject obj = &vp.toObject(); JS_ASSERT(obj->isCrossCompartmentWrapper()); JS_ASSERT(obj->getParent() == global); } return true; } if (vp.isString()) { Rooted<JSLinearString *> str(cx, vp.toString()->ensureLinear(cx)); if (!str) return false; UnrootedString wrapped = js_NewStringCopyN<CanGC>(cx, str->chars(), str->length()); if (!wrapped) return false; vp.setString(wrapped); if (!putWrapper(key, vp)) return false; if (str->zone()->isGCMarking()) { /* * All string wrappers are dropped when collection starts, but we * just created a new one. Mark the wrapped string to stop it being * finalized, because if it was then the pointer in this * compartment's wrapper map would be left dangling. */ JSString *tmp = str; MarkStringUnbarriered(&rt->gcMarker, &tmp, "wrapped string"); JS_ASSERT(tmp == str); } return true; } RootedObject proto(cx, Proxy::LazyProto); RootedObject obj(cx, &vp.toObject()); RootedObject existing(cx, existingArg); if (existing) { /* Is it possible to reuse |existing|? */ if (!existing->getTaggedProto().isLazy() || existing->getClass() != &ObjectProxyClass || existing->getParent() != global || obj->isCallable()) { existing = NULL; } } /* * We hand in the original wrapped object into the wrap hook to allow * the wrap hook to reason over what wrappers are currently applied * to the object. */ RootedObject wrapper(cx); wrapper = cx->runtime->wrapObjectCallback(cx, existing, obj, proto, global, flags); if (!wrapper) return false; // We maintain the invariant that the key in the cross-compartment wrapper // map is always directly wrapped by the value. JS_ASSERT(Wrapper::wrappedObject(wrapper) == &key.get().toObject()); vp.setObject(*wrapper); return putWrapper(key, vp); }
static bool PreprocessValue(JSContext *cx, HandleObject holder, KeyType key, MutableHandleValue vp, StringifyContext *scx) { RootedString keyStr(cx); /* Step 2. */ if (vp.isObject()) { RootedValue toJSON(cx); RootedObject obj(cx, &vp.toObject()); if (!JSObject::getProperty(cx, obj, obj, cx->names().toJSON, &toJSON)) return false; if (js_IsCallable(toJSON)) { keyStr = KeyStringifier<KeyType>::toString(cx, key); if (!keyStr) return false; InvokeArgsGuard args; if (!cx->stack.pushInvokeArgs(cx, 1, &args)) return false; args.setCallee(toJSON); args.setThis(vp); args[0] = StringValue(keyStr); if (!Invoke(cx, args)) return false; vp.set(args.rval()); } } /* Step 3. */ if (scx->replacer && scx->replacer->isCallable()) { if (!keyStr) { keyStr = KeyStringifier<KeyType>::toString(cx, key); if (!keyStr) return false; } InvokeArgsGuard args; if (!cx->stack.pushInvokeArgs(cx, 2, &args)) return false; args.setCallee(ObjectValue(*scx->replacer)); args.setThis(ObjectValue(*holder)); args[0] = StringValue(keyStr); args[1] = vp; if (!Invoke(cx, args)) return false; vp.set(args.rval()); } /* Step 4. */ if (vp.get().isObject()) { RootedObject obj(cx, &vp.get().toObject()); if (ObjectClassIs(obj, ESClass_Number, cx)) { double d; if (!ToNumber(cx, vp, &d)) return false; vp.set(NumberValue(d)); } else if (ObjectClassIs(obj, ESClass_String, cx)) { JSString *str = ToStringSlow<CanGC>(cx, vp); if (!str) return false; vp.set(StringValue(str)); } else if (ObjectClassIs(obj, ESClass_Boolean, cx)) { if (!BooleanGetPrimitiveValue(cx, obj, vp.address())) return false; JS_ASSERT(vp.get().isBoolean()); } } return true; }
/* ES6 24.3.2. */ bool js::Stringify(JSContext* cx, MutableHandleValue vp, JSObject* replacer_, const Value& space_, StringBuffer& sb, StringifyBehavior stringifyBehavior) { RootedObject replacer(cx, replacer_); RootedValue space(cx, space_); MOZ_ASSERT_IF(stringifyBehavior == StringifyBehavior::RestrictedSafe, space.isNull()); MOZ_ASSERT_IF(stringifyBehavior == StringifyBehavior::RestrictedSafe, vp.isObject()); /** * This uses MOZ_ASSERT, since it's actually asserting something jsapi * consumers could get wrong, so needs a better error message. */ MOZ_ASSERT(stringifyBehavior == StringifyBehavior::Normal || vp.toObject().is<PlainObject>() || vp.toObject().is<ArrayObject>(), "input to JS::ToJSONMaybeSafely must be a plain object or array"); /* Step 4. */ AutoIdVector propertyList(cx); if (replacer) { bool isArray; if (replacer->isCallable()) { /* Step 4a(i): use replacer to transform values. */ } else if (!IsArray(cx, replacer, &isArray)) { return false; } else if (isArray) { /* Step 4b(iii). */ /* Step 4b(iii)(2-3). */ uint32_t len; if (!GetLengthProperty(cx, replacer, &len)) return false; // Cap the initial size to a moderately small value. This avoids // ridiculous over-allocation if an array with bogusly-huge length // is passed in. If we end up having to add elements past this // size, the set will naturally resize to accommodate them. const uint32_t MaxInitialSize = 32; Rooted<GCHashSet<jsid>> idSet(cx, GCHashSet<jsid>(cx)); if (!idSet.init(Min(len, MaxInitialSize))) return false; /* Step 4b(iii)(4). */ uint32_t k = 0; /* Step 4b(iii)(5). */ RootedValue item(cx); for (; k < len; k++) { if (!CheckForInterrupt(cx)) return false; /* Step 4b(iii)(5)(a-b). */ if (!GetElement(cx, replacer, replacer, k, &item)) return false; RootedId id(cx); /* Step 4b(iii)(5)(c-f). */ if (item.isNumber()) { /* Step 4b(iii)(5)(e). */ int32_t n; if (ValueFitsInInt32(item, &n) && INT_FITS_IN_JSID(n)) { id = INT_TO_JSID(n); } else { if (!ValueToId<CanGC>(cx, item, &id)) return false; } } else { bool shouldAdd = item.isString(); if (!shouldAdd) { ESClass cls; if (!GetClassOfValue(cx, item, &cls)) return false; shouldAdd = cls == ESClass::String || cls == ESClass::Number; } if (shouldAdd) { /* Step 4b(iii)(5)(f). */ if (!ValueToId<CanGC>(cx, item, &id)) return false; } else { /* Step 4b(iii)(5)(g). */ continue; } } /* Step 4b(iii)(5)(g). */ auto p = idSet.lookupForAdd(id); if (!p) { /* Step 4b(iii)(5)(g)(i). */ if (!idSet.add(p, id) || !propertyList.append(id)) return false; } } } else { replacer = nullptr; } } /* Step 5. */ if (space.isObject()) { RootedObject spaceObj(cx, &space.toObject()); ESClass cls; if (!GetBuiltinClass(cx, spaceObj, &cls)) return false; if (cls == ESClass::Number) { double d; if (!ToNumber(cx, space, &d)) return false; space = NumberValue(d); } else if (cls == ESClass::String) { JSString* str = ToStringSlow<CanGC>(cx, space); if (!str) return false; space = StringValue(str); } } StringBuffer gap(cx); if (space.isNumber()) { /* Step 6. */ double d; MOZ_ALWAYS_TRUE(ToInteger(cx, space, &d)); d = Min(10.0, d); if (d >= 1 && !gap.appendN(' ', uint32_t(d))) return false; } else if (space.isString()) { /* Step 7. */ JSLinearString* str = space.toString()->ensureLinear(cx); if (!str) return false; size_t len = Min(size_t(10), str->length()); if (!gap.appendSubstring(str, 0, len)) return false; } else { /* Step 8. */ MOZ_ASSERT(gap.empty()); } /* Step 9. */ RootedPlainObject wrapper(cx, NewBuiltinClassInstance<PlainObject>(cx)); if (!wrapper) return false; /* Steps 10-11. */ RootedId emptyId(cx, NameToId(cx->names().empty)); if (!NativeDefineProperty(cx, wrapper, emptyId, vp, nullptr, nullptr, JSPROP_ENUMERATE)) return false; /* Step 12. */ StringifyContext scx(cx, sb, gap, replacer, propertyList, stringifyBehavior == StringifyBehavior::RestrictedSafe); if (!scx.init()) return false; if (!PreprocessValue(cx, wrapper, HandleId(emptyId), vp, &scx)) return false; if (IsFilteredValue(vp)) return true; return Str(cx, vp, &scx); }
static bool PreprocessValue(JSContext* cx, HandleObject holder, KeyType key, MutableHandleValue vp, StringifyContext* scx) { // We don't want to do any preprocessing here if scx->maybeSafely, // since the stuff we do here can have side-effects. if (scx->maybeSafely) return true; RootedString keyStr(cx); /* Step 2. */ if (vp.isObject()) { RootedValue toJSON(cx); RootedObject obj(cx, &vp.toObject()); if (!GetProperty(cx, obj, obj, cx->names().toJSON, &toJSON)) return false; if (IsCallable(toJSON)) { keyStr = KeyStringifier<KeyType>::toString(cx, key); if (!keyStr) return false; RootedValue arg0(cx, StringValue(keyStr)); if (!js::Call(cx, toJSON, vp, arg0, vp)) return false; } } /* Step 3. */ if (scx->replacer && scx->replacer->isCallable()) { if (!keyStr) { keyStr = KeyStringifier<KeyType>::toString(cx, key); if (!keyStr) return false; } RootedValue arg0(cx, StringValue(keyStr)); RootedValue replacerVal(cx, ObjectValue(*scx->replacer)); if (!js::Call(cx, replacerVal, holder, arg0, vp, vp)) return false; } /* Step 4. */ if (vp.get().isObject()) { RootedObject obj(cx, &vp.get().toObject()); ESClass cls; if (!GetBuiltinClass(cx, obj, &cls)) return false; if (cls == ESClass::Number) { double d; if (!ToNumber(cx, vp, &d)) return false; vp.setNumber(d); } else if (cls == ESClass::String) { JSString* str = ToStringSlow<CanGC>(cx, vp); if (!str) return false; vp.setString(str); } else if (cls == ESClass::Boolean) { if (!Unbox(cx, obj, vp)) return false; } } return true; }