WrappedFunctionObject.cpp (11697B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "builtin/WrappedFunctionObject.h" 8 9 #include <string_view> 10 11 #include "jsapi.h" 12 13 #include "builtin/ShadowRealm.h" 14 #include "js/CallAndConstruct.h" 15 #include "js/Class.h" 16 #include "js/ErrorReport.h" 17 #include "js/Exception.h" 18 #include "js/TypeDecls.h" 19 #include "js/Value.h" 20 #include "util/StringBuilder.h" 21 #include "vm/Compartment.h" 22 #include "vm/Interpreter.h" 23 #include "vm/JSFunction.h" 24 #include "vm/ObjectOperations.h" 25 26 #include "vm/JSFunction-inl.h" 27 #include "vm/JSObject-inl.h" 28 #include "vm/Realm-inl.h" 29 30 using namespace js; 31 using namespace JS; 32 33 // GetWrappedValue ( callerRealm: a Realm Record, value: unknown ) 34 bool js::GetWrappedValue(JSContext* cx, Realm* callerRealm, Handle<Value> value, 35 MutableHandle<Value> res) { 36 cx->check(value); 37 38 // Step 2. Return value (Reordered) 39 if (!value.isObject()) { 40 res.set(value); 41 return true; 42 } 43 44 // Step 1. If Type(value) is Object, then 45 // a. If IsCallable(value) is false, throw a TypeError exception. 46 Rooted<JSObject*> objectVal(cx, &value.toObject()); 47 if (!IsCallable(objectVal)) { 48 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 49 JSMSG_SHADOW_REALM_INVALID_RETURN); 50 return false; 51 } 52 53 // b. Return ? WrappedFunctionCreate(callerRealm, value). 54 return WrappedFunctionCreate(cx, callerRealm, objectVal, res); 55 } 56 57 // [[Call]] 58 // https://tc39.es/proposal-shadowrealm/#sec-wrapped-function-exotic-objects-call-thisargument-argumentslist 59 // https://tc39.es/proposal-shadowrealm/#sec-ordinary-wrapped-function-call 60 static bool WrappedFunction_Call(JSContext* cx, unsigned argc, Value* vp) { 61 CallArgs args = CallArgsFromVp(argc, vp); 62 63 Rooted<JSObject*> callee(cx, &args.callee()); 64 MOZ_ASSERT(callee->is<WrappedFunctionObject>()); 65 66 Handle<WrappedFunctionObject*> fun = callee.as<WrappedFunctionObject>(); 67 68 // PrepareForWrappedFunctionCall is a no-op in our implementation, because 69 // we've already entered the correct realm. 70 MOZ_ASSERT(cx->realm() == fun->realm()); 71 72 // The next steps refer to the OrdinaryWrappedFunctionCall operation. 73 74 // 1. Let target be F.[[WrappedTargetFunction]]. 75 Rooted<JSObject*> target(cx, fun->getTargetFunction()); 76 77 // 2. Assert: IsCallable(target) is true. 78 MOZ_ASSERT(IsCallable(ObjectValue(*target))); 79 80 // 3. Let callerRealm be F.[[Realm]]. 81 Rooted<Realm*> callerRealm(cx, fun->realm()); 82 83 // 4. NOTE: Any exception objects produced after this point are associated 84 // with callerRealm. 85 // 86 // Implicit in our implementation, because |callerRealm| is already the 87 // current realm. 88 89 // 5. Let targetRealm be ? GetFunctionRealm(target). 90 Rooted<Realm*> targetRealm(cx, GetFunctionRealm(cx, target)); 91 if (!targetRealm) { 92 return false; 93 } 94 95 // 6. Let wrappedArgs be a new empty List. 96 InvokeArgs wrappedArgs(cx); 97 if (!wrappedArgs.init(cx, args.length())) { 98 return false; 99 } 100 101 // 7. For each element arg of argumentsList, do 102 // a. Let wrappedValue be ? GetWrappedValue(targetRealm, arg). 103 // b. Append wrappedValue to wrappedArgs. 104 Rooted<Value> element(cx); 105 for (size_t i = 0; i < args.length(); i++) { 106 element = args.get(i); 107 if (!GetWrappedValue(cx, targetRealm, element, &element)) { 108 return false; 109 } 110 111 wrappedArgs[i].set(element); 112 } 113 114 // 8. Let wrappedThisArgument to ? GetWrappedValue(targetRealm, 115 // thisArgument). 116 Rooted<Value> wrappedThisArgument(cx); 117 if (!GetWrappedValue(cx, targetRealm, args.thisv(), &wrappedThisArgument)) { 118 return false; 119 } 120 121 // 9. Let result be the Completion Record of Call(target, 122 // wrappedThisArgument, wrappedArgs). 123 Rooted<Value> targetValue(cx, ObjectValue(*target)); 124 Rooted<Value> result(cx); 125 if (!js::Call(cx, targetValue, wrappedThisArgument, wrappedArgs, &result)) { 126 // 11. Else (reordered); 127 // a. Throw a TypeError exception. 128 ReportPotentiallyDetailedMessage( 129 cx, JSMSG_SHADOW_REALM_WRAPPED_EXECUTION_FAILURE_DETAIL, 130 JSMSG_SHADOW_REALM_WRAPPED_EXECUTION_FAILURE); 131 return false; 132 } 133 134 // 10. If result.[[Type]] is normal or result.[[Type]] is return, then 135 // a. Return ? GetWrappedValue(callerRealm, result.[[Value]]). 136 if (!GetWrappedValue(cx, callerRealm, result, args.rval())) { 137 return false; 138 } 139 140 return true; 141 } 142 143 static bool CopyNameAndLength(JSContext* cx, HandleObject fun, 144 HandleObject target) { 145 // 1. If argCount is undefined, then set argCount to 0 (implicit) 146 constexpr int32_t argCount = 0; 147 148 // 2. Let L be 0. 149 double length = 0; 150 151 // 3. Let targetHasLength be ? HasOwnProperty(Target, "length"). 152 // 153 // Try to avoid invoking the resolve hook. 154 // Also see ComputeLengthValue in BoundFunctionObject.cpp. 155 if (target->is<JSFunction>() && 156 !target->as<JSFunction>().hasResolvedLength()) { 157 uint16_t targetLen; 158 if (!JSFunction::getUnresolvedLength(cx, target.as<JSFunction>(), 159 &targetLen)) { 160 return false; 161 } 162 163 length = std::max(0.0, double(targetLen) - argCount); 164 } else { 165 Rooted<jsid> lengthId(cx, NameToId(cx->names().length)); 166 167 bool targetHasLength; 168 if (!HasOwnProperty(cx, target, lengthId, &targetHasLength)) { 169 return false; 170 } 171 172 // 4. If targetHasLength is true, then 173 if (targetHasLength) { 174 // a. Let targetLen be ? Get(Target, "length"). 175 Rooted<Value> targetLen(cx); 176 if (!GetProperty(cx, target, target, lengthId, &targetLen)) { 177 return false; 178 } 179 180 // b. If Type(targetLen) is Number, then 181 // i. If targetLen is +∞𝔽, set L to +∞. 182 // ii. Else if targetLen is -∞𝔽, set L to 0. 183 // iii. Else, 184 // 1. Let targetLenAsInt be ! ToIntegerOrInfinity(targetLen). 185 // 2. Assert: targetLenAsInt is finite. 186 // 3. Set L to max(targetLenAsInt - argCount, 0). 187 if (targetLen.isNumber()) { 188 length = std::max(0.0, JS::ToInteger(targetLen.toNumber()) - argCount); 189 } 190 } 191 } 192 193 // 5. Perform ! SetFunctionLength(F, L). 194 Rooted<Value> rootedLength(cx, NumberValue(length)); 195 if (!DefineDataProperty(cx, fun, cx->names().length, rootedLength, 196 JSPROP_READONLY)) { 197 return false; 198 } 199 200 // 6. Let targetName be ? Get(Target, "name"). 201 // 202 // Try to avoid invoking the resolve hook. 203 Rooted<Value> targetName(cx); 204 if (target->is<JSFunction>() && !target->as<JSFunction>().hasResolvedName()) { 205 JSFunction* targetFun = &target->as<JSFunction>(); 206 JSString* targetNameStr = targetFun->getUnresolvedName(cx); 207 if (!targetNameStr) { 208 return false; 209 } 210 targetName.setString(targetNameStr); 211 } else { 212 if (!GetProperty(cx, target, target, cx->names().name, &targetName)) { 213 return false; 214 } 215 } 216 217 // 7. If Type(targetName) is not String, set targetName to the empty String. 218 if (!targetName.isString()) { 219 targetName = StringValue(cx->runtime()->emptyString); 220 } 221 222 // 8. Perform ! SetFunctionName(F, targetName, prefix). 223 return DefineDataProperty(cx, fun, cx->names().name, targetName, 224 JSPROP_READONLY); 225 } 226 227 static JSString* ToStringOp(JSContext* cx, JS::HandleObject obj, 228 bool isToSource) { 229 // Return an unnamed native function to match the behavior of bound 230 // functions. 231 // 232 // NOTE: The current value of the "name" property can be any value, it's not 233 // necessarily a string value. It can also be an accessor property which could 234 // lead to executing side-effects, which isn't allowed per the spec, cf. 235 // <https://tc39.es/ecma262/#sec-function.prototype.tostring>. Even if it's a 236 // data property with a string value, we'd still need to validate the string 237 // can be parsed as a |PropertyName| production before using it as part of the 238 // output. 239 constexpr std::string_view nativeCode = "function () {\n [native code]\n}"; 240 241 return NewStringCopy<CanGC>(cx, nativeCode); 242 } 243 244 static const JSClassOps classOps = { 245 nullptr, // addProperty 246 nullptr, // delProperty 247 nullptr, // enumerate 248 nullptr, // newEnumerate 249 nullptr, // resolve 250 nullptr, // mayResolve 251 nullptr, // finalize 252 WrappedFunction_Call, // call 253 nullptr, // construct 254 nullptr, // trace 255 }; 256 257 static const ObjectOps objOps = { 258 nullptr, // lookupProperty 259 nullptr, // defineProperty 260 nullptr, // hasProperty 261 nullptr, // getProperty 262 nullptr, // setProperty 263 nullptr, // getOwnPropertyDescriptor 264 nullptr, // deleteProperty 265 nullptr, // getElements 266 ToStringOp, // funToString 267 }; 268 269 const JSClass WrappedFunctionObject::class_ = { 270 "WrappedFunctionObject", 271 JSCLASS_HAS_CACHED_PROTO( 272 JSProto_Function) | // This sets the prototype to Function.prototype, 273 // Step 3 of WrappedFunctionCreate 274 JSCLASS_HAS_RESERVED_SLOTS(WrappedFunctionObject::SlotCount), 275 &classOps, 276 JS_NULL_CLASS_SPEC, 277 JS_NULL_CLASS_EXT, 278 &objOps, 279 }; 280 281 // WrappedFunctionCreate ( callerRealm: a Realm Record, Target: a function 282 // object) 283 bool js::WrappedFunctionCreate(JSContext* cx, Realm* callerRealm, 284 HandleObject target, MutableHandle<Value> res) { 285 cx->check(target); 286 287 WrappedFunctionObject* wrapped = nullptr; 288 { 289 // Ensure that the function object has the correct realm by allocating it 290 // into that realm. 291 Rooted<JSObject*> global(cx, callerRealm->maybeGlobal()); 292 MOZ_RELEASE_ASSERT( 293 global, "global is null; executing in a realm that's being GC'd?"); 294 AutoRealm ar(cx, global); 295 296 MOZ_ASSERT(target); 297 298 // Target *could* be a function from another compartment. 299 Rooted<JSObject*> maybeWrappedTarget(cx, target); 300 if (!cx->compartment()->wrap(cx, &maybeWrappedTarget)) { 301 return false; 302 } 303 304 // 1. Let internalSlotsList be the internal slots listed in Table 2, plus 305 // [[Prototype]] and [[Extensible]]. 306 // 2. Let wrapped be ! MakeBasicObject(internalSlotsList). 307 // 3. Set wrapped.[[Prototype]] to 308 // callerRealm.[[Intrinsics]].[[%Function.prototype%]]. 309 wrapped = NewBuiltinClassInstance<WrappedFunctionObject>(cx); 310 if (!wrapped) { 311 return false; 312 } 313 314 // 4. Set wrapped.[[Call]] as described in 2.1 (implicit in JSClass call 315 // hook) 316 // 5. Set wrapped.[[WrappedTargetFunction]] to Target. 317 wrapped->setTargetFunction(*maybeWrappedTarget); 318 // 6. Set wrapped.[[Realm]] to callerRealm. (implicitly the realm of 319 // wrapped, which we assured with the AutoRealm 320 321 MOZ_ASSERT(wrapped->realm() == callerRealm); 322 } 323 324 // Wrap |wrapped| to the current compartment. 325 RootedObject obj(cx, wrapped); 326 if (!cx->compartment()->wrap(cx, &obj)) { 327 return false; 328 } 329 330 // 7. Let result be CopyNameAndLength(wrapped, Target). 331 if (!CopyNameAndLength(cx, obj, target)) { 332 // 8. If result is an Abrupt Completion, throw a TypeError exception. 333 cx->clearPendingException(); 334 335 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 336 JSMSG_SHADOW_REALM_WRAP_FAILURE); 337 return false; 338 } 339 340 // 9. Return wrapped. 341 res.set(ObjectValue(*obj)); 342 return true; 343 }