Compartment-inl.h (12089B)
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 vm_Compartment_inl_h 8 #define vm_Compartment_inl_h 9 10 #include "vm/Compartment.h" 11 12 #include "jsapi.h" 13 #include "jsfriendapi.h" 14 #include "jsnum.h" 15 #include "js/CallArgs.h" 16 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 17 #include "js/Wrapper.h" 18 #include "vm/Iteration.h" 19 #include "vm/JSObject.h" 20 21 #include "vm/JSContext-inl.h" 22 23 struct JSClass; 24 25 inline js::StringWrapperMap::Ptr JS::Compartment::lookupWrapper( 26 JSString* str) const { 27 return zone()->crossZoneStringWrappers().lookup(str); 28 } 29 30 inline bool JS::Compartment::wrap(JSContext* cx, JS::MutableHandleValue vp) { 31 /* Only GC things have to be wrapped or copied. */ 32 if (!vp.isGCThing()) { 33 return true; 34 } 35 36 /* 37 * Symbols are GC things, but never need to be wrapped or copied because 38 * they are always allocated in the atoms zone. They still need to be 39 * marked in the new compartment's zone, however. 40 */ 41 if (vp.isSymbol()) { 42 cx->markAtomValue(vp); 43 return true; 44 } 45 46 /* Handle strings. */ 47 if (vp.isString()) { 48 JS::RootedString str(cx, vp.toString()); 49 if (!wrap(cx, &str)) { 50 return false; 51 } 52 vp.setString(str); 53 return true; 54 } 55 56 if (vp.isBigInt()) { 57 JS::RootedBigInt bi(cx, vp.toBigInt()); 58 if (!wrap(cx, &bi)) { 59 return false; 60 } 61 vp.setBigInt(bi); 62 return true; 63 } 64 65 MOZ_ASSERT(vp.isObject()); 66 67 /* 68 * All that's left are objects. 69 * 70 * Object wrapping isn't the fastest thing in the world, in part because 71 * we have to unwrap and invoke the prewrap hook to find the identity 72 * object before we even start checking the cache. Neither of these 73 * operations are needed in the common case, where we're just wrapping 74 * a plain JS object from the wrappee's side of the membrane to the 75 * wrapper's side. 76 * 77 * To optimize this, we note that the cache should only ever contain 78 * identity objects - that is to say, objects that serve as the 79 * canonical representation for a unique object identity observable by 80 * script. Unwrap and prewrap are both steps that we take to get to the 81 * identity of an incoming objects, and as such, they shuld never map 82 * one identity object to another object. This means that we can safely 83 * check the cache immediately, and only risk false negatives. Do this 84 * in opt builds, and do both in debug builds so that we can assert 85 * that we get the same answer. 86 */ 87 #ifdef DEBUG 88 JS::AssertValueIsNotGray(vp); 89 JS::RootedObject cacheResult(cx); 90 #endif 91 if (js::ObjectWrapperMap::Ptr p = lookupWrapper(&vp.toObject())) { 92 #ifdef DEBUG 93 cacheResult = p->value().get(); 94 #else 95 vp.setObject(*p->value().get()); 96 return true; 97 #endif 98 } 99 100 JS::RootedObject obj(cx, &vp.toObject()); 101 if (!wrap(cx, &obj)) { 102 return false; 103 } 104 vp.setObject(*obj); 105 MOZ_ASSERT_IF(cacheResult, obj == cacheResult); 106 return true; 107 } 108 109 inline bool JS::Compartment::wrap(JSContext* cx, 110 MutableHandle<mozilla::Maybe<Value>> vp) { 111 if (vp.get().isNothing()) { 112 return true; 113 } 114 115 return wrap(cx, MutableHandle<Value>::fromMarkedLocation(vp.get().ptr())); 116 } 117 118 namespace js { 119 namespace detail { 120 121 /** 122 * Return the name of class T as a static null-terminated ASCII string constant 123 * (for error messages). 124 */ 125 template <class T> 126 const char* ClassName() { 127 return T::class_.name; 128 } 129 130 template <class T, class ErrorCallback> 131 [[nodiscard]] T* UnwrapAndTypeCheckValueSlowPath(JSContext* cx, 132 HandleValue value, 133 ErrorCallback throwTypeError) { 134 JSObject* obj = nullptr; 135 if (value.isObject()) { 136 obj = &value.toObject(); 137 if (IsWrapper(obj)) { 138 obj = CheckedUnwrapStatic(obj); 139 if (!obj) { 140 ReportAccessDenied(cx); 141 return nullptr; 142 } 143 } 144 } 145 146 if (!obj || !obj->is<T>()) { 147 throwTypeError(); 148 return nullptr; 149 } 150 151 return &obj->as<T>(); 152 } 153 154 template <class ErrorCallback> 155 [[nodiscard]] JSObject* UnwrapAndTypeCheckValueSlowPath( 156 JSContext* cx, HandleValue value, const JSClass* clasp, 157 ErrorCallback throwTypeError) { 158 JSObject* obj = nullptr; 159 if (value.isObject()) { 160 obj = &value.toObject(); 161 if (IsWrapper(obj)) { 162 obj = CheckedUnwrapStatic(obj); 163 if (!obj) { 164 ReportAccessDenied(cx); 165 return nullptr; 166 } 167 } 168 } 169 170 if (!obj || !obj->hasClass(clasp)) { 171 throwTypeError(); 172 return nullptr; 173 } 174 175 return obj; 176 } 177 178 } // namespace detail 179 180 /** 181 * Remove all wrappers from `val` and try to downcast the result to class `T`. 182 * 183 * DANGER: The result may not be same-compartment with `cx`. 184 * 185 * This calls `throwTypeError` if the value isn't an object, cannot be 186 * unwrapped, or isn't an instance of the expected type. `throwTypeError` must 187 * in fact throw a TypeError (or OOM trying). 188 */ 189 template <class T, class ErrorCallback> 190 [[nodiscard]] inline T* UnwrapAndTypeCheckValue(JSContext* cx, 191 HandleValue value, 192 ErrorCallback throwTypeError) { 193 cx->check(value); 194 195 static_assert(!std::is_convertible_v<T*, Wrapper*>, 196 "T can't be a Wrapper type; this function discards wrappers"); 197 198 if (value.isObject() && value.toObject().is<T>()) { 199 return &value.toObject().as<T>(); 200 } 201 202 return detail::UnwrapAndTypeCheckValueSlowPath<T>(cx, value, throwTypeError); 203 } 204 205 /** 206 * Remove all wrappers from |val| and try to downcast the result to an object of 207 * the class |clasp|. 208 * 209 * DANGER: The result may not be same-compartment with |cx|. 210 * 211 * This calls |throwTypeError| if the value isn't an object, cannot be 212 * unwrapped, or isn't an instance of the expected type. |throwTypeError| must 213 * in fact throw a TypeError (or OOM trying). 214 */ 215 template <class ErrorCallback> 216 [[nodiscard]] inline JSObject* UnwrapAndTypeCheckValue( 217 JSContext* cx, HandleValue value, const JSClass* clasp, 218 ErrorCallback throwTypeError) { 219 cx->check(value); 220 221 if (value.isObject() && value.toObject().hasClass(clasp)) { 222 return &value.toObject(); 223 } 224 225 return detail::UnwrapAndTypeCheckValueSlowPath(cx, value, clasp, 226 throwTypeError); 227 } 228 229 /** 230 * Remove all wrappers from `args.thisv()` and try to downcast the result to 231 * class `T`. 232 * 233 * DANGER: The result may not be same-compartment with `cx`. 234 * 235 * This throws a TypeError if the value isn't an object, cannot be unwrapped, 236 * or isn't an instance of the expected type. 237 */ 238 template <class T> 239 [[nodiscard]] inline T* UnwrapAndTypeCheckThis(JSContext* cx, 240 const CallArgs& args, 241 const char* methodName) { 242 HandleValue thisv = args.thisv(); 243 return UnwrapAndTypeCheckValue<T>(cx, thisv, [cx, methodName, thisv] { 244 JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, 245 JSMSG_INCOMPATIBLE_PROTO, detail::ClassName<T>(), 246 methodName, InformalValueTypeName(thisv)); 247 }); 248 } 249 250 /** 251 * Remove all wrappers from `args[argIndex]` and try to downcast the result to 252 * class `T`. 253 * 254 * DANGER: The result may not be same-compartment with `cx`. 255 * 256 * This throws a TypeError if the specified argument is missing, isn't an 257 * object, cannot be unwrapped, or isn't an instance of the expected type. 258 */ 259 template <class T> 260 [[nodiscard]] inline T* UnwrapAndTypeCheckArgument(JSContext* cx, 261 CallArgs& args, 262 const char* methodName, 263 int argIndex) { 264 HandleValue val = args.get(argIndex); 265 return UnwrapAndTypeCheckValue<T>(cx, val, [cx, val, methodName, argIndex] { 266 Int32ToCStringBuf cbuf; 267 char* numStr = Int32ToCString(&cbuf, argIndex + 1); 268 MOZ_ASSERT(numStr); 269 JS_ReportErrorNumberLatin1( 270 cx, GetErrorMessage, nullptr, JSMSG_WRONG_TYPE_ARG, numStr, methodName, 271 detail::ClassName<T>(), InformalValueTypeName(val)); 272 }); 273 } 274 275 /** 276 * Unwrap an object of a known type. 277 * 278 * If `obj` is an object of class T, this returns a pointer to that object. If 279 * `obj` is a wrapper for such an object, this tries to unwrap the object and 280 * return a pointer to it. If access is denied, or `obj` was a wrapper but has 281 * been nuked, this reports an error and returns null. 282 * 283 * In all other cases, the behavior is undefined, so call this only if `obj` is 284 * known to have been an object of class T, or a wrapper to a T, at some point. 285 */ 286 template <class T> 287 [[nodiscard]] inline T* UnwrapAndDowncastObject(JSContext* cx, JSObject* obj) { 288 static_assert(!std::is_convertible_v<T*, Wrapper*>, 289 "T can't be a Wrapper type; this function discards wrappers"); 290 291 if (IsProxy(obj)) { 292 if (JS_IsDeadWrapper(obj)) { 293 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 294 JSMSG_DEAD_OBJECT); 295 return nullptr; 296 } 297 298 // It would probably be OK to do an unchecked unwrap here, but we allow 299 // arbitrary security policies, so check anyway. 300 obj = obj->maybeUnwrapAs<T>(); 301 if (!obj) { 302 ReportAccessDenied(cx); 303 return nullptr; 304 } 305 } 306 307 return &obj->as<T>(); 308 } 309 310 /** 311 * Unwrap an object of a known (but not compile-time-known) class. 312 * 313 * If |obj| is an object with class |clasp|, this returns |obj|. If |obj| is a 314 * wrapper for such an object, this tries to unwrap the object and return a 315 * pointer to it. If access is denied, or |obj| was a wrapper but has been 316 * nuked, this reports an error and returns null. 317 * 318 * In all other cases, the behavior is undefined, so call this only if |obj| is 319 * known to have had class |clasp|, or been a wrapper to such an object, at some 320 * point. 321 */ 322 [[nodiscard]] inline JSObject* UnwrapAndDowncastObject(JSContext* cx, 323 JSObject* obj, 324 const JSClass* clasp) { 325 if (IsProxy(obj)) { 326 if (JS_IsDeadWrapper(obj)) { 327 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 328 JSMSG_DEAD_OBJECT); 329 return nullptr; 330 } 331 332 // It would probably be OK to do an unchecked unwrap here, but we allow 333 // arbitrary security policies, so check anyway. 334 obj = obj->maybeUnwrapAs(clasp); 335 if (!obj) { 336 ReportAccessDenied(cx); 337 return nullptr; 338 } 339 } 340 341 MOZ_ASSERT(obj->hasClass(clasp)); 342 return obj; 343 } 344 345 /** 346 * Unwrap a value of a known type. See UnwrapAndDowncastObject. 347 */ 348 template <class T> 349 [[nodiscard]] inline T* UnwrapAndDowncastValue(JSContext* cx, 350 const Value& value) { 351 return UnwrapAndDowncastObject<T>(cx, &value.toObject()); 352 } 353 354 /** 355 * Unwrap an object of a known (but not compile-time-known) class. See 356 * UnwrapAndDowncastObject. 357 */ 358 [[nodiscard]] inline JSObject* UnwrapAndDowncastValue(JSContext* cx, 359 const Value& value, 360 const JSClass* clasp) { 361 return UnwrapAndDowncastObject(cx, &value.toObject(), clasp); 362 } 363 364 } // namespace js 365 366 MOZ_ALWAYS_INLINE bool JS::Compartment::objectMaybeInIteration(JSObject* obj) { 367 MOZ_ASSERT(obj->compartment() == this); 368 369 js::NativeIteratorListIter iter(&enumerators_); 370 371 // If the list is empty, we're not iterating any objects. 372 if (iter.done()) { 373 return false; 374 } 375 376 // If the list contains a single object, check if it's |obj|. 377 js::NativeIterator* next = iter.next(); 378 if (iter.done()) { 379 return next->objectBeingIterated() == obj; 380 } 381 382 return true; 383 } 384 385 #endif /* vm_Compartment_inl_h */