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