JSContext-inl.h (10672B)
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_JSContext_inl_h 8 #define vm_JSContext_inl_h 9 10 #include "vm/JSContext.h" 11 12 #include <type_traits> 13 14 #include "gc/Marking.h" 15 #include "gc/Zone.h" 16 #include "jit/JitFrames.h" 17 #include "util/DiagnosticAssertions.h" 18 #include "vm/BigIntType.h" 19 #include "vm/GlobalObject.h" 20 #include "vm/Realm.h" 21 22 #include "gc/Allocator-inl.h" 23 #include "vm/Activation-inl.h" // js::Activation::hasWasmExitFP 24 25 namespace js { 26 27 class ContextChecks { 28 JSContext* cx; 29 30 JS::Realm* realm() const { return cx->realm(); } 31 JS::Compartment* compartment() const { return cx->compartment(); } 32 JS::Zone* zone() const { return cx->zone(); } 33 34 public: 35 explicit ContextChecks(JSContext* cx) : cx(cx) { 36 #ifdef DEBUG 37 if (realm()) { 38 GlobalObject* global = realm()->unsafeUnbarrieredMaybeGlobal(); 39 if (global) { 40 checkObject(global); 41 } 42 } 43 #endif 44 } 45 46 /* 47 * Set a breakpoint here (break js::ContextChecks::fail) to debug 48 * realm/compartment/zone mismatches. 49 */ 50 static void fail(JS::Realm* r1, JS::Realm* r2, int argIndex) { 51 MOZ_CRASH_UNSAFE_PRINTF("*** Realm mismatch %p vs. %p at argument %d", r1, 52 r2, argIndex); 53 } 54 static void fail(JS::Compartment* c1, JS::Compartment* c2, int argIndex) { 55 MOZ_CRASH_UNSAFE_PRINTF("*** Compartment mismatch %p vs. %p at argument %d", 56 c1, c2, argIndex); 57 } 58 static void fail(JS::Zone* z1, JS::Zone* z2, int argIndex) { 59 MOZ_CRASH_UNSAFE_PRINTF("*** Zone mismatch %p vs. %p at argument %d", z1, 60 z2, argIndex); 61 } 62 63 void check(JS::Realm* r, int argIndex) { 64 if (r && MOZ_UNLIKELY(r != realm())) { 65 fail(realm(), r, argIndex); 66 } 67 } 68 69 void check(JS::Compartment* c, int argIndex) { 70 if (c && MOZ_UNLIKELY(c != compartment())) { 71 fail(compartment(), c, argIndex); 72 } 73 } 74 75 void check(JS::Zone* z, int argIndex) { 76 if (zone() && MOZ_UNLIKELY(z != zone())) { 77 fail(zone(), z, argIndex); 78 } 79 } 80 81 void check(JSObject* obj, int argIndex) { 82 if (obj) { 83 checkObject(obj); 84 check(obj->compartment(), argIndex); 85 } 86 } 87 88 void checkObject(JSObject* obj) { 89 JS::AssertObjectIsNotGray(obj); 90 MOZ_ASSERT(!js::gc::IsAboutToBeFinalizedUnbarriered(obj)); 91 } 92 93 template <typename T> 94 void checkAtom(T* thing, int argIndex) { 95 static_assert(std::is_same_v<T, JSAtom> || std::is_same_v<T, JS::Symbol>, 96 "Should only be called with JSAtom* or JS::Symbol* argument"); 97 98 JS::AssertCellIsNotGray(thing); 99 100 #ifdef DEBUG 101 // Atoms which move across zone boundaries need to be marked in the atom 102 // marking bitmap for the new zone, see JS_MarkCrossZoneId. 103 // Note that the atom marking state may not be up-to-date if incremental 104 // marking is taking place. 105 gc::GCRuntime* gc = &cx->runtime()->gc; 106 bool isGCMarking = 107 gc->state() >= gc::State::Prepare && gc->state() <= gc::State::Sweep; 108 if (zone() && !isGCMarking) { 109 gc::CellColor color = gc->atomMarking.getAtomMarkColor(zone(), thing); 110 if (color != gc::CellColor::Black) { 111 MOZ_CRASH_UNSAFE_PRINTF( 112 "*** Atom is marked %s for zone %p at argument %d", 113 gc::CellColorName(color), zone(), argIndex); 114 } 115 } 116 #endif 117 } 118 119 void check(JSString* str, int argIndex) { 120 JS::AssertCellIsNotGray(str); 121 if (str->isAtom()) { 122 checkAtom(&str->asAtom(), argIndex); 123 } else { 124 check(str->zone(), argIndex); 125 } 126 } 127 128 void check(JS::Symbol* symbol, int argIndex) { checkAtom(symbol, argIndex); } 129 130 void check(JS::BigInt* bi, int argIndex) { check(bi->zone(), argIndex); } 131 132 void check(const js::Value& v, int argIndex) { 133 if (v.isObject()) { 134 check(&v.toObject(), argIndex); 135 } else if (v.isString()) { 136 check(v.toString(), argIndex); 137 } else if (v.isSymbol()) { 138 check(v.toSymbol(), argIndex); 139 } else if (v.isBigInt()) { 140 check(v.toBigInt(), argIndex); 141 } 142 } 143 144 // Check the contents of any container class that supports the C++ 145 // iteration protocol, eg GCVector<jsid>. 146 template <typename Container> 147 std::enable_if_t<std::is_same_v<decltype(std::declval<Container>().begin()), 148 decltype(std::declval<Container>().end())>> 149 check(const Container& container, int argIndex) { 150 for (auto i : container) { 151 check(i, argIndex); 152 } 153 } 154 155 void check(const JS::HandleValueArray& arr, int argIndex) { 156 for (size_t i = 0; i < arr.length(); i++) { 157 check(arr[i], argIndex); 158 } 159 } 160 161 void check(const CallArgs& args, int argIndex) { 162 for (Value* p = args.base(); p != args.end(); ++p) { 163 check(*p, argIndex); 164 } 165 } 166 167 void check(jsid id, int argIndex) { 168 if (id.isAtom()) { 169 checkAtom(id.toAtom(), argIndex); 170 } else if (id.isSymbol()) { 171 checkAtom(id.toSymbol(), argIndex); 172 } else { 173 MOZ_ASSERT(!id.isGCThing()); 174 } 175 } 176 177 void check(JSScript* script, int argIndex) { 178 JS::AssertCellIsNotGray(script); 179 if (script) { 180 check(script->realm(), argIndex); 181 } 182 } 183 184 void check(AbstractFramePtr frame, int argIndex); 185 186 void check(const PropertyDescriptor& desc, int argIndex) { 187 if (desc.hasGetter()) { 188 check(desc.getter(), argIndex); 189 } 190 if (desc.hasSetter()) { 191 check(desc.setter(), argIndex); 192 } 193 if (desc.hasValue()) { 194 check(desc.value(), argIndex); 195 } 196 } 197 198 void check(Handle<mozilla::Maybe<Value>> maybe, int argIndex) { 199 if (maybe.get().isSome()) { 200 check(maybe.get().ref(), argIndex); 201 } 202 } 203 204 void check(Handle<mozilla::Maybe<PropertyDescriptor>> maybe, int argIndex) { 205 if (maybe.get().isSome()) { 206 check(maybe.get().ref(), argIndex); 207 } 208 } 209 }; 210 211 } // namespace js 212 213 template <class... Args> 214 inline void JSContext::checkImpl(const Args&... args) { 215 int argIndex = 0; 216 (..., js::ContextChecks(this).check(args, argIndex++)); 217 } 218 219 template <class... Args> 220 inline void JSContext::check(const Args&... args) { 221 #ifdef JS_CRASH_DIAGNOSTICS 222 if (contextChecksEnabled()) { 223 checkImpl(args...); 224 } 225 #endif 226 } 227 228 template <class... Args> 229 inline void JSContext::releaseCheck(const Args&... args) { 230 if (contextChecksEnabled()) { 231 checkImpl(args...); 232 } 233 } 234 235 template <class... Args> 236 MOZ_ALWAYS_INLINE void JSContext::debugOnlyCheck(const Args&... args) { 237 #if defined(DEBUG) && defined(JS_CRASH_DIAGNOSTICS) 238 if (contextChecksEnabled()) { 239 checkImpl(args...); 240 } 241 #endif 242 } 243 244 namespace js { 245 246 STATIC_PRECONDITION_ASSUME(ubound(args.argv_) >= argc) 247 MOZ_ALWAYS_INLINE bool CallNativeImpl(JSContext* cx, NativeImpl impl, 248 const CallArgs& args) { 249 #ifdef DEBUG 250 bool alreadyThrowing = cx->isExceptionPending(); 251 #endif 252 cx->check(args); 253 bool ok = impl(cx, args); 254 if (ok) { 255 cx->check(args.rval()); 256 MOZ_ASSERT_IF(!alreadyThrowing, !cx->isExceptionPending()); 257 } 258 return ok; 259 } 260 261 // OOM interrupts don't call the interrupt callbacks, so we don't need 262 // to worry about if there is an exception pending. We do want to handle 263 // the interrupt sooner than for other interrupts so we capture a precise 264 // stack trace. 265 MOZ_ALWAYS_INLINE bool CheckForOOMStackTraceInterrupt(JSContext* cx) { 266 if (MOZ_UNLIKELY(cx->hasPendingInterrupt(InterruptReason::OOMStackTrace))) { 267 return cx->handleInterruptNoCallbacks(); 268 } 269 return true; 270 } 271 272 MOZ_ALWAYS_INLINE bool CheckForInterrupt(JSContext* cx) { 273 MOZ_ASSERT(!cx->isExceptionPending()); 274 // Add an inline fast-path since we have to check for interrupts in some hot 275 // C++ loops of library builtins. 276 if (MOZ_UNLIKELY(cx->hasAnyPendingInterrupt())) { 277 return cx->handleInterrupt(); 278 } 279 280 JS_INTERRUPT_POSSIBLY_FAIL(); 281 282 return true; 283 } 284 285 } /* namespace js */ 286 287 inline void JSContext::minorGC(JS::GCReason reason) { 288 runtime()->gc.minorGC(reason); 289 } 290 291 inline bool JSContext::runningWithTrustedPrincipals() { 292 if (!realm()) { 293 return true; 294 } 295 if (!runtime()->trustedPrincipals()) { 296 return false; 297 } 298 return realm()->principals() == runtime()->trustedPrincipals(); 299 } 300 301 inline void JSContext::enterRealm(JS::Realm* realm) { 302 // We should never enter a realm while in the atoms zone. 303 MOZ_ASSERT_IF(zone(), !zone()->isAtomsZone()); 304 305 realm->enter(); 306 setRealm(realm); 307 } 308 309 inline void JSContext::enterAtomsZone() { 310 realm_ = nullptr; 311 setZone(runtime_->unsafeAtomsZone()); 312 } 313 314 inline void JSContext::setZone(js::Zone* zone) { zone_ = zone; } 315 316 inline void JSContext::enterRealmOf(JSObject* target) { 317 JS::AssertCellIsNotGray(target); 318 enterRealm(target->nonCCWRealm()); 319 } 320 321 inline void JSContext::enterRealmOf(JSScript* target) { 322 JS::AssertCellIsNotGray(target); 323 enterRealm(target->realm()); 324 } 325 326 inline void JSContext::enterRealmOf(js::Shape* target) { 327 JS::AssertCellIsNotGray(target); 328 enterRealm(target->realm()); 329 } 330 331 inline void JSContext::enterNullRealm() { 332 // We should never enter a realm while in the atoms zone. 333 MOZ_ASSERT_IF(zone(), !zone()->isAtomsZone()); 334 335 setRealm(nullptr); 336 } 337 338 inline void JSContext::leaveRealm(JS::Realm* oldRealm) { 339 // Only call leave() after we've setRealm()-ed away from the current realm. 340 JS::Realm* startingRealm = realm_; 341 342 // The current realm should be marked as entered-from-C++ at this point. 343 MOZ_ASSERT_IF(startingRealm, startingRealm->hasBeenEnteredIgnoringJit()); 344 345 setRealm(oldRealm); 346 347 if (startingRealm) { 348 startingRealm->leave(); 349 } 350 } 351 352 inline void JSContext::leaveAtomsZone(JS::Realm* oldRealm) { 353 setRealm(oldRealm); 354 } 355 356 inline void JSContext::setRealm(JS::Realm* realm) { 357 realm_ = realm; 358 if (realm) { 359 // This thread must have exclusive access to the zone. 360 MOZ_ASSERT(CurrentThreadCanAccessZone(realm->zone())); 361 MOZ_ASSERT(!realm->zone()->isAtomsZone()); 362 setZone(realm->zone()); 363 } else { 364 setZone(nullptr); 365 } 366 } 367 368 inline void JSContext::setRealmForJitExceptionHandler(JS::Realm* realm) { 369 // JIT code enters (same-compartment) realms without calling realm->enter() 370 // so we don't call realm->leave() here. 371 MOZ_ASSERT(realm->compartment() == compartment()); 372 realm_ = realm; 373 } 374 375 inline js::RuntimeCaches& JSContext::caches() { return runtime()->caches(); } 376 377 template <typename T, js::AllowGC allowGC, typename... Args> 378 T* JSContext::newCell(Args&&... args) { 379 return js::gc::CellAllocator::template NewCell<T, allowGC>( 380 this, std::forward<Args>(args)...); 381 } 382 383 #endif /* vm_JSContext_inl_h */