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