XrayWrapper.h (19879B)
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 #ifndef XrayWrapper_h 8 #define XrayWrapper_h 9 10 #include "mozilla/Maybe.h" 11 12 #include "WrapperFactory.h" 13 14 #include "jsapi.h" 15 #include "jsfriendapi.h" 16 #include "js/friend/XrayJitInfo.h" // JS::XrayJitInfo 17 #include "js/Object.h" // JS::GetReservedSlot 18 #include "js/Proxy.h" 19 #include "js/Wrapper.h" 20 #include "mozilla/dom/ScriptSettings.h" 21 22 // Slot where Xray functions for Web IDL methods store a pointer to 23 // the Xray wrapper they're associated with. 24 #define XRAY_DOM_FUNCTION_PARENT_WRAPPER_SLOT 0 25 // Slot where in debug builds Xray functions for Web IDL methods store 26 // a pointer to their themselves, just so we can assert that they're the 27 // sort of functions we expect. 28 #define XRAY_DOM_FUNCTION_NATIVE_SLOT_FOR_SELF 1 29 30 // Xray wrappers re-resolve the original native properties on the native 31 // object and always directly access to those properties. 32 // Because they work so differently from the rest of the wrapper hierarchy, 33 // we pull them out of the Wrapper inheritance hierarchy and create a 34 // little world around them. 35 36 class nsIPrincipal; 37 38 namespace xpc { 39 40 enum XrayType { 41 XrayForDOMObject, 42 XrayForJSObject, 43 XrayForOpaqueObject, 44 NotXray 45 }; 46 47 class XrayTraits { 48 public: 49 constexpr XrayTraits() = default; 50 51 static JSObject* getTargetObject(JSObject* wrapper) { 52 JSObject* target = 53 js::UncheckedUnwrap(wrapper, /* stopAtWindowProxy = */ false); 54 if (target) { 55 JS::ExposeObjectToActiveJS(target); 56 } 57 return target; 58 } 59 60 // NB: resolveOwnProperty may decide whether or not to cache what it finds 61 // on the holder. If the result is not cached, the lookup will happen afresh 62 // for each access, which is the right thing for things like dynamic NodeList 63 // properties. 64 virtual bool resolveOwnProperty( 65 JSContext* cx, JS::HandleObject wrapper, JS::HandleObject target, 66 JS::HandleObject holder, JS::HandleId id, 67 JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc); 68 69 bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, 70 JS::ObjectOpResult& result) { 71 return result.succeed(); 72 } 73 74 static bool getBuiltinClass(JSContext* cx, JS::HandleObject wrapper, 75 const js::Wrapper& baseInstance, 76 js::ESClass* cls) { 77 return baseInstance.getBuiltinClass(cx, wrapper, cls); 78 } 79 80 static const char* className(JSContext* cx, JS::HandleObject wrapper, 81 const js::Wrapper& baseInstance) { 82 return baseInstance.className(cx, wrapper); 83 } 84 85 virtual void preserveWrapper(JSObject* target) = 0; 86 87 bool getExpandoObject(JSContext* cx, JS::HandleObject target, 88 JS::HandleObject consumer, 89 JS::MutableHandleObject expandObject); 90 JSObject* ensureExpandoObject(JSContext* cx, JS::HandleObject wrapper, 91 JS::HandleObject target); 92 93 // Slots for holder objects. 94 enum { 95 HOLDER_SLOT_CACHED_PROTO = 0, 96 HOLDER_SLOT_EXPANDO = 1, 97 HOLDER_SHARED_SLOT_COUNT 98 }; 99 100 static JSObject* getHolder(JSObject* wrapper); 101 JSObject* ensureHolder(JSContext* cx, JS::HandleObject wrapper); 102 virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) = 0; 103 104 JSObject* getExpandoChain(JS::HandleObject obj); 105 JSObject* detachExpandoChain(JS::HandleObject obj); 106 bool setExpandoChain(JSContext* cx, JS::HandleObject obj, 107 JS::HandleObject chain); 108 bool cloneExpandoChain(JSContext* cx, JS::HandleObject dst, 109 JS::HandleObject srcChain); 110 111 protected: 112 static const JSClass HolderClass; 113 114 // Get the JSClass we should use for our expando object. 115 virtual const JSClass* getExpandoClass(JSContext* cx, 116 JS::HandleObject target) const; 117 118 private: 119 bool expandoObjectMatchesConsumer(JSContext* cx, 120 JS::HandleObject expandoObject, 121 nsIPrincipal* consumerOrigin); 122 123 // |expandoChain| is the expando chain in the wrapped object's compartment. 124 // |exclusiveWrapper| is any xray that has exclusive use of the expando. 125 // |cx| may be in any compartment. 126 bool getExpandoObjectInternal(JSContext* cx, JSObject* expandoChain, 127 JS::HandleObject exclusiveWrapper, 128 nsIPrincipal* origin, 129 JS::MutableHandleObject expandoObject); 130 131 // |cx| is in the target's compartment, and |exclusiveWrapper| is any xray 132 // that has exclusive use of the expando. |exclusiveWrapperGlobal| is the 133 // caller's global and must be same-compartment with |exclusiveWrapper|. 134 JSObject* attachExpandoObject(JSContext* cx, JS::HandleObject target, 135 JS::HandleObject exclusiveWrapper, 136 JS::HandleObject exclusiveWrapperGlobal, 137 nsIPrincipal* origin); 138 139 XrayTraits(XrayTraits&) = delete; 140 const XrayTraits& operator=(XrayTraits&) = delete; 141 }; 142 143 void ExpandoObjectFinalize(JS::GCContext* gcx, JSObject* obj); 144 145 class DOMXrayTraits : public XrayTraits { 146 public: 147 constexpr DOMXrayTraits() = default; 148 149 static const XrayType Type = XrayForDOMObject; 150 151 virtual bool resolveOwnProperty( 152 JSContext* cx, JS::HandleObject wrapper, JS::HandleObject target, 153 JS::HandleObject holder, JS::HandleId id, 154 JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) override; 155 156 bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, 157 JS::ObjectOpResult& result); 158 159 bool defineProperty( 160 JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, 161 JS::Handle<JS::PropertyDescriptor> desc, 162 JS::Handle<mozilla::Maybe<JS::PropertyDescriptor>> existingDesc, 163 JS::Handle<JSObject*> existingHolder, JS::ObjectOpResult& result, 164 bool* done); 165 virtual bool enumerateNames(JSContext* cx, JS::HandleObject wrapper, 166 unsigned flags, JS::MutableHandleIdVector props); 167 static bool call(JSContext* cx, JS::HandleObject wrapper, 168 const JS::CallArgs& args, const js::Wrapper& baseInstance); 169 static bool construct(JSContext* cx, JS::HandleObject wrapper, 170 const JS::CallArgs& args, 171 const js::Wrapper& baseInstance); 172 173 static bool getPrototype(JSContext* cx, JS::HandleObject wrapper, 174 JS::HandleObject target, 175 JS::MutableHandleObject protop); 176 177 virtual void preserveWrapper(JSObject* target) override; 178 179 virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) override; 180 181 static DOMXrayTraits singleton; 182 183 protected: 184 virtual const JSClass* getExpandoClass( 185 JSContext* cx, JS::HandleObject target) const override; 186 }; 187 188 class JSXrayTraits : public XrayTraits { 189 public: 190 static const XrayType Type = XrayForJSObject; 191 192 virtual bool resolveOwnProperty( 193 JSContext* cx, JS::HandleObject wrapper, JS::HandleObject target, 194 JS::HandleObject holder, JS::HandleId id, 195 JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) override; 196 197 bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, 198 JS::ObjectOpResult& result); 199 200 bool defineProperty( 201 JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, 202 JS::Handle<JS::PropertyDescriptor> desc, 203 JS::Handle<mozilla::Maybe<JS::PropertyDescriptor>> existingDesc, 204 JS::Handle<JSObject*> existingHolder, JS::ObjectOpResult& result, 205 bool* defined); 206 207 virtual bool enumerateNames(JSContext* cx, JS::HandleObject wrapper, 208 unsigned flags, JS::MutableHandleIdVector props); 209 210 static bool call(JSContext* cx, JS::HandleObject wrapper, 211 const JS::CallArgs& args, const js::Wrapper& baseInstance) { 212 JSXrayTraits& self = JSXrayTraits::singleton; 213 JS::RootedObject holder(cx, self.ensureHolder(cx, wrapper)); 214 if (!holder) { 215 return false; 216 } 217 JSProtoKey key = xpc::JSXrayTraits::getProtoKey(holder); 218 if (key == JSProto_Function || key == JSProto_BoundFunction) { 219 return baseInstance.call(cx, wrapper, args); 220 } 221 222 JS::RootedValue v(cx, JS::ObjectValue(*wrapper)); 223 js::ReportIsNotFunction(cx, v); 224 return false; 225 } 226 227 static bool construct(JSContext* cx, JS::HandleObject wrapper, 228 const JS::CallArgs& args, 229 const js::Wrapper& baseInstance); 230 231 bool getPrototype(JSContext* cx, JS::HandleObject wrapper, 232 JS::HandleObject target, JS::MutableHandleObject protop) { 233 JS::RootedObject holder(cx, ensureHolder(cx, wrapper)); 234 if (!holder) { 235 return false; 236 } 237 JSProtoKey key = getProtoKey(holder); 238 if (isPrototype(holder)) { 239 JSProtoKey protoKey = js::InheritanceProtoKeyForStandardClass(key); 240 if (protoKey == JSProto_Null) { 241 protop.set(nullptr); 242 return true; 243 } 244 key = protoKey; 245 } 246 247 { 248 JSAutoRealm ar(cx, target); 249 if (!JS_GetClassPrototype(cx, key, protop)) { 250 return false; 251 } 252 } 253 return JS_WrapObject(cx, protop); 254 } 255 256 virtual void preserveWrapper(JSObject* target) override { 257 // In the case of pure JS objects, there is no underlying object, and 258 // the target is the canonical representation of state. If it gets 259 // collected, then expandos and such should be collected too. So there's 260 // nothing to do here. 261 } 262 263 enum { 264 SLOT_PROTOKEY = HOLDER_SHARED_SLOT_COUNT, 265 SLOT_ISPROTOTYPE, 266 SLOT_CONSTRUCTOR_FOR, 267 SLOT_COUNT 268 }; 269 virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) override; 270 271 static JSProtoKey getProtoKey(JSObject* holder) { 272 int32_t key = JS::GetReservedSlot(holder, SLOT_PROTOKEY).toInt32(); 273 return static_cast<JSProtoKey>(key); 274 } 275 276 static bool isPrototype(JSObject* holder) { 277 return JS::GetReservedSlot(holder, SLOT_ISPROTOTYPE).toBoolean(); 278 } 279 280 static JSProtoKey constructorFor(JSObject* holder) { 281 int32_t key = JS::GetReservedSlot(holder, SLOT_CONSTRUCTOR_FOR).toInt32(); 282 return static_cast<JSProtoKey>(key); 283 } 284 285 // Operates in the wrapper compartment. 286 static bool getOwnPropertyFromWrapperIfSafe( 287 JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, 288 JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc); 289 290 // Like the above, but operates in the target compartment. wrapperGlobal is 291 // the caller's global (must be in the wrapper compartment). 292 static bool getOwnPropertyFromTargetIfSafe( 293 JSContext* cx, JS::HandleObject target, JS::HandleObject wrapper, 294 JS::HandleObject wrapperGlobal, JS::HandleId id, 295 JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc); 296 297 static const JSClass HolderClass; 298 static JSXrayTraits singleton; 299 }; 300 301 // These traits are used when the target is not Xrayable and we therefore want 302 // to make it opaque modulo the usual Xray machinery (like expandos and 303 // .wrappedJSObject). 304 class OpaqueXrayTraits : public XrayTraits { 305 public: 306 static const XrayType Type = XrayForOpaqueObject; 307 308 virtual bool resolveOwnProperty( 309 JSContext* cx, JS::HandleObject wrapper, JS::HandleObject target, 310 JS::HandleObject holder, JS::HandleId id, 311 JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) override; 312 313 bool defineProperty( 314 JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, 315 JS::Handle<JS::PropertyDescriptor> desc, 316 JS::Handle<mozilla::Maybe<JS::PropertyDescriptor>> existingDesc, 317 JS::Handle<JSObject*> existingHolder, JS::ObjectOpResult& result, 318 bool* defined) { 319 *defined = false; 320 return true; 321 } 322 323 virtual bool enumerateNames(JSContext* cx, JS::HandleObject wrapper, 324 unsigned flags, JS::MutableHandleIdVector props) { 325 return true; 326 } 327 328 static bool call(JSContext* cx, JS::HandleObject wrapper, 329 const JS::CallArgs& args, const js::Wrapper& baseInstance) { 330 JS::RootedValue v(cx, JS::ObjectValue(*wrapper)); 331 js::ReportIsNotFunction(cx, v); 332 return false; 333 } 334 335 static bool construct(JSContext* cx, JS::HandleObject wrapper, 336 const JS::CallArgs& args, 337 const js::Wrapper& baseInstance) { 338 JS::RootedValue v(cx, JS::ObjectValue(*wrapper)); 339 js::ReportIsNotFunction(cx, v); 340 return false; 341 } 342 343 bool getPrototype(JSContext* cx, JS::HandleObject wrapper, 344 JS::HandleObject target, JS::MutableHandleObject protop) { 345 // Opaque wrappers just get targetGlobal.Object.prototype as their 346 // prototype. This is preferable to using a null prototype because it 347 // lets things like |toString| and |__proto__| work. 348 { 349 JSAutoRealm ar(cx, target); 350 if (!JS_GetClassPrototype(cx, JSProto_Object, protop)) { 351 return false; 352 } 353 } 354 return JS_WrapObject(cx, protop); 355 } 356 357 static bool getBuiltinClass(JSContext* cx, JS::HandleObject wrapper, 358 const js::Wrapper& baseInstance, 359 js::ESClass* cls) { 360 *cls = js::ESClass::Other; 361 return true; 362 } 363 364 static const char* className(JSContext* cx, JS::HandleObject wrapper, 365 const js::Wrapper& baseInstance) { 366 return "Opaque"; 367 } 368 369 virtual void preserveWrapper(JSObject* target) override {} 370 371 virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) override { 372 return JS_NewObjectWithGivenProto(cx, &HolderClass, nullptr); 373 } 374 375 static OpaqueXrayTraits singleton; 376 }; 377 378 XrayType GetXrayType(JSObject* obj); 379 XrayTraits* GetXrayTraits(JSObject* obj); 380 381 template <typename Base, typename Traits> 382 class XrayWrapper : public Base { 383 static_assert(std::is_base_of_v<js::BaseProxyHandler, Base>, 384 "Base *must* derive from js::BaseProxyHandler"); 385 386 public: 387 constexpr explicit XrayWrapper(unsigned flags) 388 : Base(flags | WrapperFactory::IS_XRAY_WRAPPER_FLAG, 389 /* aHasPrototype = */ true) {}; 390 391 /* Standard internal methods. */ 392 virtual bool getOwnPropertyDescriptor( 393 JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id, 394 JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc) 395 const override; 396 virtual bool defineProperty(JSContext* cx, JS::Handle<JSObject*> wrapper, 397 JS::Handle<jsid> id, 398 JS::Handle<JS::PropertyDescriptor> desc, 399 JS::ObjectOpResult& result) const override; 400 virtual bool ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper, 401 JS::MutableHandleIdVector props) const override; 402 virtual bool delete_(JSContext* cx, JS::Handle<JSObject*> wrapper, 403 JS::Handle<jsid> id, 404 JS::ObjectOpResult& result) const override; 405 virtual bool enumerate(JSContext* cx, JS::Handle<JSObject*> wrapper, 406 JS::MutableHandleIdVector props) const override; 407 virtual bool getPrototype(JSContext* cx, JS::HandleObject wrapper, 408 JS::MutableHandleObject protop) const override; 409 virtual bool setPrototype(JSContext* cx, JS::HandleObject wrapper, 410 JS::HandleObject proto, 411 JS::ObjectOpResult& result) const override; 412 virtual bool getPrototypeIfOrdinary( 413 JSContext* cx, JS::HandleObject wrapper, bool* isOrdinary, 414 JS::MutableHandleObject protop) const override; 415 virtual bool setImmutablePrototype(JSContext* cx, JS::HandleObject wrapper, 416 bool* succeeded) const override; 417 virtual bool preventExtensions(JSContext* cx, JS::Handle<JSObject*> wrapper, 418 JS::ObjectOpResult& result) const override; 419 virtual bool isExtensible(JSContext* cx, JS::Handle<JSObject*> wrapper, 420 bool* extensible) const override; 421 virtual bool has(JSContext* cx, JS::Handle<JSObject*> wrapper, 422 JS::Handle<jsid> id, bool* bp) const override; 423 virtual bool get(JSContext* cx, JS::Handle<JSObject*> wrapper, 424 JS::HandleValue receiver, JS::Handle<jsid> id, 425 JS::MutableHandle<JS::Value> vp) const override; 426 virtual bool set(JSContext* cx, JS::Handle<JSObject*> wrapper, 427 JS::Handle<jsid> id, JS::Handle<JS::Value> v, 428 JS::Handle<JS::Value> receiver, 429 JS::ObjectOpResult& result) const override; 430 virtual bool call(JSContext* cx, JS::Handle<JSObject*> wrapper, 431 const JS::CallArgs& args) const override; 432 virtual bool construct(JSContext* cx, JS::Handle<JSObject*> wrapper, 433 const JS::CallArgs& args) const override; 434 435 /* SpiderMonkey extensions. */ 436 virtual bool hasOwn(JSContext* cx, JS::Handle<JSObject*> wrapper, 437 JS::Handle<jsid> id, bool* bp) const override; 438 virtual bool getOwnEnumerablePropertyKeys( 439 JSContext* cx, JS::Handle<JSObject*> wrapper, 440 JS::MutableHandleIdVector props) const override; 441 442 virtual bool getBuiltinClass(JSContext* cx, JS::HandleObject wapper, 443 js::ESClass* cls) const override; 444 virtual const char* className(JSContext* cx, 445 JS::HandleObject proxy) const override; 446 447 static const XrayWrapper singleton; 448 449 protected: 450 bool getPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper, 451 unsigned flags, JS::MutableHandleIdVector props) const; 452 }; 453 454 #define PermissiveXrayDOM \ 455 xpc::XrayWrapper<js::CrossCompartmentWrapper, xpc::DOMXrayTraits> 456 #define PermissiveXrayJS \ 457 xpc::XrayWrapper<js::CrossCompartmentWrapper, xpc::JSXrayTraits> 458 #define PermissiveXrayOpaque \ 459 xpc::XrayWrapper<js::CrossCompartmentWrapper, xpc::OpaqueXrayTraits> 460 461 extern template class PermissiveXrayDOM; 462 extern template class PermissiveXrayJS; 463 extern template class PermissiveXrayOpaque; 464 465 /* 466 * Slots for Xray expando objects. See comments in XrayWrapper.cpp for details 467 * of how these get used; we mostly want the value of JSSLOT_EXPANDO_COUNT here. 468 */ 469 enum ExpandoSlots { 470 JSSLOT_EXPANDO_NEXT = 0, 471 JSSLOT_EXPANDO_ORIGIN, 472 JSSLOT_EXPANDO_EXCLUSIVE_WRAPPER_HOLDER, 473 JSSLOT_EXPANDO_PROTOTYPE, 474 JSSLOT_EXPANDO_COUNT 475 }; 476 477 extern const JSClassOps XrayExpandoObjectClassOps; 478 479 /* 480 * Call aFunc on all Xray expandos for the given object. aFunc will be passed a 481 * JSObject pointer to an Xray expando object. 482 * 483 * No-op when called on non-main threads (where Xrays don't exist). 484 */ 485 template <typename F> 486 void ForEachXrayExpandoObject(JS::RootingContext* aCx, JSObject* aTarget, 487 F&& aFunc) { 488 if (!NS_IsMainThread()) { 489 // No Xrays 490 return; 491 } 492 493 MOZ_ASSERT(GetXrayTraits(aTarget) == &DOMXrayTraits::singleton); 494 JS::RootedObject rootedTarget(aCx, aTarget); 495 JS::RootedObject head(aCx, 496 DOMXrayTraits::singleton.getExpandoChain(rootedTarget)); 497 while (head) { 498 aFunc(head); 499 head = JS::GetReservedSlot(head, JSSLOT_EXPANDO_NEXT).toObjectOrNull(); 500 } 501 } 502 503 /* 504 * Ensure the given wrapper has an expando object and return it. This can 505 * return null on failure. Will only be called when "wrapper" is an Xray for a 506 * DOM object. 507 */ 508 JSObject* EnsureXrayExpandoObject(JSContext* cx, JS::HandleObject wrapper); 509 510 // Information about xrays for use by the JITs. 511 extern JS::XrayJitInfo gXrayJitInfo; 512 513 } // namespace xpc 514 515 #endif