/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "builtin/WrappedFunctionObject.h" #include #include "jsapi.h" #include "builtin/ShadowRealm.h" #include "js/CallAndConstruct.h" #include "js/Class.h" #include "js/ErrorReport.h" #include "js/Exception.h" #include "js/TypeDecls.h" #include "js/Value.h" #include "util/StringBuffer.h" #include "vm/Compartment.h" #include "vm/Interpreter.h" #include "vm/JSFunction.h" #include "vm/ObjectOperations.h" #include "vm/JSFunction-inl.h" #include "vm/JSObject-inl.h" #include "vm/Realm-inl.h" using namespace js; using namespace JS; // GetWrappedValue ( callerRealm: a Realm Record, value: unknown ) bool js::GetWrappedValue(JSContext* cx, Realm* callerRealm, Handle value, MutableHandle res) { cx->check(value); // Step 2. Return value (Reordered) if (!value.isObject()) { res.set(value); return true; } // Step 1. If Type(value) is Object, then // a. If IsCallable(value) is false, throw a TypeError exception. Rooted objectVal(cx, &value.toObject()); if (!IsCallable(objectVal)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SHADOW_REALM_INVALID_RETURN); return false; } // b. Return ? WrappedFunctionCreate(callerRealm, value). return WrappedFunctionCreate(cx, callerRealm, objectVal, res); } // [[Call]] // https://tc39.es/proposal-shadowrealm/#sec-wrapped-function-exotic-objects-call-thisargument-argumentslist // https://tc39.es/proposal-shadowrealm/#sec-ordinary-wrapped-function-call static bool WrappedFunction_Call(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); Rooted callee(cx, &args.callee()); MOZ_ASSERT(callee->is()); Handle fun = callee.as(); // PrepareForWrappedFunctionCall is a no-op in our implementation, because // we've already entered the correct realm. MOZ_ASSERT(cx->realm() == fun->realm()); // The next steps refer to the OrdinaryWrappedFunctionCall operation. // 1. Let target be F.[[WrappedTargetFunction]]. Rooted target(cx, fun->getTargetFunction()); // 2. Assert: IsCallable(target) is true. MOZ_ASSERT(IsCallable(ObjectValue(*target))); // 3. Let callerRealm be F.[[Realm]]. Rooted callerRealm(cx, fun->realm()); // 4. NOTE: Any exception objects produced after this point are associated // with callerRealm. // // Implicit in our implementation, because |callerRealm| is already the // current realm. // 5. Let targetRealm be ? GetFunctionRealm(target). Rooted targetRealm(cx, GetFunctionRealm(cx, target)); if (!targetRealm) { return false; } // 6. Let wrappedArgs be a new empty List. InvokeArgs wrappedArgs(cx); if (!wrappedArgs.init(cx, args.length())) { return false; } // 7. For each element arg of argumentsList, do // a. Let wrappedValue be ? GetWrappedValue(targetRealm, arg). // b. Append wrappedValue to wrappedArgs. Rooted element(cx); for (size_t i = 0; i < args.length(); i++) { element = args.get(i); if (!GetWrappedValue(cx, targetRealm, element, &element)) { return false; } wrappedArgs[i].set(element); } // 8. Let wrappedThisArgument to ? GetWrappedValue(targetRealm, // thisArgument). Rooted wrappedThisArgument(cx); if (!GetWrappedValue(cx, targetRealm, args.thisv(), &wrappedThisArgument)) { return false; } // 9. Let result be the Completion Record of Call(target, // wrappedThisArgument, wrappedArgs). Rooted targetValue(cx, ObjectValue(*target)); Rooted result(cx); if (!js::Call(cx, targetValue, wrappedThisArgument, wrappedArgs, &result)) { // 11. Else (reordered); // a. Throw a TypeError exception. ReportPotentiallyDetailedMessage( cx, JSMSG_SHADOW_REALM_WRAPPED_EXECUTION_FAILURE_DETAIL, JSMSG_SHADOW_REALM_WRAPPED_EXECUTION_FAILURE); return false; } // 10. If result.[[Type]] is normal or result.[[Type]] is return, then // a. Return ? GetWrappedValue(callerRealm, result.[[Value]]). if (!GetWrappedValue(cx, callerRealm, result, args.rval())) { return false; } return true; } static bool CopyNameAndLength(JSContext* cx, HandleObject fun, HandleObject target) { // 1. If argCount is undefined, then set argCount to 0 (implicit) constexpr int32_t argCount = 0; // 2. Let L be 0. double length = 0; // 3. Let targetHasLength be ? HasOwnProperty(Target, "length"). // // Try to avoid invoking the resolve hook. // Also see ComputeLengthValue in BoundFunctionObject.cpp. if (target->is() && !target->as().hasResolvedLength()) { uint16_t targetLen; if (!JSFunction::getUnresolvedLength(cx, target.as(), &targetLen)) { return false; } length = std::max(0.0, double(targetLen) - argCount); } else { Rooted lengthId(cx, NameToId(cx->names().length)); bool targetHasLength; if (!HasOwnProperty(cx, target, lengthId, &targetHasLength)) { return false; } // 4. If targetHasLength is true, then if (targetHasLength) { // a. Let targetLen be ? Get(Target, "length"). Rooted targetLen(cx); if (!GetProperty(cx, target, target, lengthId, &targetLen)) { return false; } // b. If Type(targetLen) is Number, then // i. If targetLen is +∞𝔽, set L to +∞. // ii. Else if targetLen is -∞𝔽, set L to 0. // iii. Else, // 1. Let targetLenAsInt be ! ToIntegerOrInfinity(targetLen). // 2. Assert: targetLenAsInt is finite. // 3. Set L to max(targetLenAsInt - argCount, 0). if (targetLen.isNumber()) { length = std::max(0.0, JS::ToInteger(targetLen.toNumber()) - argCount); } } } // 5. Perform ! SetFunctionLength(F, L). Rooted rootedLength(cx, NumberValue(length)); if (!DefineDataProperty(cx, fun, cx->names().length, rootedLength, JSPROP_READONLY)) { return false; } // 6. Let targetName be ? Get(Target, "name"). // // Try to avoid invoking the resolve hook. Rooted targetName(cx); if (target->is() && !target->as().hasResolvedName()) { JSFunction* targetFun = &target->as(); targetName.setString(targetFun->infallibleGetUnresolvedName(cx)); } else { if (!GetProperty(cx, target, target, cx->names().name, &targetName)) { return false; } } // 7. If Type(targetName) is not String, set targetName to the empty String. if (!targetName.isString()) { targetName = StringValue(cx->runtime()->emptyString); } // 8. Perform ! SetFunctionName(F, targetName, prefix). return DefineDataProperty(cx, fun, cx->names().name, targetName, JSPROP_READONLY); } static JSString* ToStringOp(JSContext* cx, JS::HandleObject obj, bool isToSource) { // Return an unnamed native function to match the behavior of bound // functions. // // NOTE: The current value of the "name" property can be any value, it's not // necessarily a string value. It can also be an accessor property which could // lead to executing side-effects, which isn't allowed per the spec, cf. // . Even if it's a // data property with a string value, we'd still need to validate the string // can be parsed as a |PropertyName| production before using it as part of the // output. constexpr std::string_view nativeCode = "function () {\n [native code]\n}"; return NewStringCopy(cx, nativeCode); } static const JSClassOps classOps = { nullptr, // addProperty nullptr, // delProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve nullptr, // finalize WrappedFunction_Call, // call nullptr, // construct nullptr, // trace }; static const ObjectOps objOps = { nullptr, // lookupProperty nullptr, // defineProperty nullptr, // hasProperty nullptr, // getProperty nullptr, // setProperty nullptr, // getOwnPropertyDescriptor nullptr, // deleteProperty nullptr, // getElements ToStringOp, // funToString }; const JSClass WrappedFunctionObject::class_ = { "WrappedFunctionObject", JSCLASS_HAS_CACHED_PROTO( JSProto_Function) | // This sets the prototype to Function.prototype, // Step 3 of WrappedFunctionCreate JSCLASS_HAS_RESERVED_SLOTS(WrappedFunctionObject::SlotCount), &classOps, JS_NULL_CLASS_SPEC, JS_NULL_CLASS_EXT, &objOps, }; // WrappedFunctionCreate ( callerRealm: a Realm Record, Target: a function // object) bool js::WrappedFunctionCreate(JSContext* cx, Realm* callerRealm, HandleObject target, MutableHandle res) { cx->check(target); WrappedFunctionObject* wrapped = nullptr; { // Ensure that the function object has the correct realm by allocating it // into that realm. Rooted global(cx, callerRealm->maybeGlobal()); MOZ_RELEASE_ASSERT( global, "global is null; executing in a realm that's being GC'd?"); AutoRealm ar(cx, global); MOZ_ASSERT(target); // Target *could* be a function from another compartment. Rooted maybeWrappedTarget(cx, target); if (!cx->compartment()->wrap(cx, &maybeWrappedTarget)) { return false; } // 1. Let internalSlotsList be the internal slots listed in Table 2, plus // [[Prototype]] and [[Extensible]]. // 2. Let wrapped be ! MakeBasicObject(internalSlotsList). // 3. Set wrapped.[[Prototype]] to // callerRealm.[[Intrinsics]].[[%Function.prototype%]]. wrapped = NewBuiltinClassInstance(cx); if (!wrapped) { return false; } // 4. Set wrapped.[[Call]] as described in 2.1 (implicit in JSClass call // hook) // 5. Set wrapped.[[WrappedTargetFunction]] to Target. wrapped->setTargetFunction(*maybeWrappedTarget); // 6. Set wrapped.[[Realm]] to callerRealm. (implicitly the realm of // wrapped, which we assured with the AutoRealm MOZ_ASSERT(wrapped->realm() == callerRealm); } // Wrap |wrapped| to the current compartment. RootedObject obj(cx, wrapped); if (!cx->compartment()->wrap(cx, &obj)) { return false; } // 7. Let result be CopyNameAndLength(wrapped, Target). if (!CopyNameAndLength(cx, obj, target)) { // 8. If result is an Abrupt Completion, throw a TypeError exception. cx->clearPendingException(); JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SHADOW_REALM_WRAP_FAILURE); return false; } // 9. Return wrapped. res.set(ObjectValue(*obj)); return true; }