diff options
Diffstat (limited to 'js/src/builtin/WrappedFunctionObject.cpp')
-rw-r--r-- | js/src/builtin/WrappedFunctionObject.cpp | 339 |
1 files changed, 339 insertions, 0 deletions
diff --git a/js/src/builtin/WrappedFunctionObject.cpp b/js/src/builtin/WrappedFunctionObject.cpp new file mode 100644 index 0000000000..97a14a4e5f --- /dev/null +++ b/js/src/builtin/WrappedFunctionObject.cpp @@ -0,0 +1,339 @@ +/* -*- 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 <string_view> + +#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> value, + MutableHandle<Value> 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<JSObject*> 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<JSObject*> callee(cx, &args.callee()); + MOZ_ASSERT(callee->is<WrappedFunctionObject>()); + + Handle<WrappedFunctionObject*> fun = callee.as<WrappedFunctionObject>(); + + // 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<JSObject*> target(cx, fun->getTargetFunction()); + + // 2. Assert: IsCallable(target) is true. + MOZ_ASSERT(IsCallable(ObjectValue(*target))); + + // 3. Let callerRealm be F.[[Realm]]. + Rooted<Realm*> 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<Realm*> 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<Value> 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<Value> wrappedThisArgument(cx); + if (!GetWrappedValue(cx, targetRealm, args.thisv(), &wrappedThisArgument)) { + return false; + } + + // 9. Let result be the Completion Record of Call(target, + // wrappedThisArgument, wrappedArgs). + Rooted<Value> targetValue(cx, ObjectValue(*target)); + Rooted<Value> 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<JSFunction>() && + !target->as<JSFunction>().hasResolvedLength()) { + uint16_t targetLen; + if (!JSFunction::getUnresolvedLength(cx, target.as<JSFunction>(), + &targetLen)) { + return false; + } + + length = std::max(0.0, double(targetLen) - argCount); + } else { + Rooted<jsid> 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<Value> 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<Value> 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<Value> targetName(cx); + if (target->is<JSFunction>() && !target->as<JSFunction>().hasResolvedName()) { + JSFunction* targetFun = &target->as<JSFunction>(); + 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. + // <https://tc39.es/ecma262/#sec-function.prototype.tostring>. 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<CanGC>(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<Value> res) { + cx->check(target); + + WrappedFunctionObject* wrapped = nullptr; + { + // Ensure that the function object has the correct realm by allocating it + // into that realm. + Rooted<JSObject*> 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<JSObject*> 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<WrappedFunctionObject>(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; +} |