1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
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 jscntxtinlines_h
8 #define jscntxtinlines_h
9
10 #include "jscntxt.h"
11 #include "jscompartment.h"
12
13 #include "jsiter.h"
14
15 #include "builtin/Object.h"
16 #include "jit/JitFrames.h"
17 #include "vm/HelperThreads.h"
18 #include "vm/Interpreter.h"
19 #include "vm/ProxyObject.h"
20 #include "vm/Symbol.h"
21
22 namespace js {
23
24 class CompartmentChecker
25 {
26 JSCompartment* compartment;
27
28 public:
CompartmentChecker(ExclusiveContext * cx)29 explicit CompartmentChecker(ExclusiveContext* cx)
30 : compartment(cx->compartment())
31 {
32 #ifdef DEBUG
33 // In debug builds, make sure the embedder passed the cx it claimed it
34 // was going to use.
35 JSContext* activeContext = nullptr;
36 if (cx->isJSContext())
37 activeContext = cx->asJSContext()->runtime()->activeContext;
38 MOZ_ASSERT_IF(activeContext, cx == activeContext);
39 #endif
40 }
41
42 /*
43 * Set a breakpoint here (break js::CompartmentChecker::fail) to debug
44 * compartment mismatches.
45 */
fail(JSCompartment * c1,JSCompartment * c2)46 static void fail(JSCompartment* c1, JSCompartment* c2) {
47 printf("*** Compartment mismatch %p vs. %p\n", (void*) c1, (void*) c2);
48 MOZ_CRASH();
49 }
50
fail(JS::Zone * z1,JS::Zone * z2)51 static void fail(JS::Zone* z1, JS::Zone* z2) {
52 printf("*** Zone mismatch %p vs. %p\n", (void*) z1, (void*) z2);
53 MOZ_CRASH();
54 }
55
56 /* Note: should only be used when neither c1 nor c2 may be the atoms compartment. */
check(JSCompartment * c1,JSCompartment * c2)57 static void check(JSCompartment* c1, JSCompartment* c2) {
58 MOZ_ASSERT(!c1->runtimeFromAnyThread()->isAtomsCompartment(c1));
59 MOZ_ASSERT(!c2->runtimeFromAnyThread()->isAtomsCompartment(c2));
60 if (c1 != c2)
61 fail(c1, c2);
62 }
63
check(JSCompartment * c)64 void check(JSCompartment* c) {
65 if (c && !compartment->runtimeFromAnyThread()->isAtomsCompartment(c)) {
66 if (!compartment)
67 compartment = c;
68 else if (c != compartment)
69 fail(compartment, c);
70 }
71 }
72
checkZone(JS::Zone * z)73 void checkZone(JS::Zone* z) {
74 if (compartment && z != compartment->zone())
75 fail(compartment->zone(), z);
76 }
77
check(JSObject * obj)78 void check(JSObject* obj) {
79 if (obj)
80 check(obj->compartment());
81 }
82
83 template<typename T>
check(const Rooted<T> & rooted)84 void check(const Rooted<T>& rooted) {
85 check(rooted.get());
86 }
87
88 template<typename T>
check(Handle<T> handle)89 void check(Handle<T> handle) {
90 check(handle.get());
91 }
92
check(JSString * str)93 void check(JSString* str) {
94 if (!str->isAtom())
95 checkZone(str->zone());
96 }
97
check(const js::Value & v)98 void check(const js::Value& v) {
99 if (v.isObject())
100 check(&v.toObject());
101 else if (v.isString())
102 check(v.toString());
103 }
104
check(const ValueArray & arr)105 void check(const ValueArray& arr) {
106 for (size_t i = 0; i < arr.length; i++)
107 check(arr.array[i]);
108 }
109
check(const JSValueArray & arr)110 void check(const JSValueArray& arr) {
111 for (size_t i = 0; i < arr.length; i++)
112 check(arr.array[i]);
113 }
114
check(const JS::HandleValueArray & arr)115 void check(const JS::HandleValueArray& arr) {
116 for (size_t i = 0; i < arr.length(); i++)
117 check(arr[i]);
118 }
119
check(const CallArgs & args)120 void check(const CallArgs& args) {
121 for (Value* p = args.base(); p != args.end(); ++p)
122 check(*p);
123 }
124
check(jsid id)125 void check(jsid id) {}
126
check(JSScript * script)127 void check(JSScript* script) {
128 if (script)
129 check(script->compartment());
130 }
131
132 void check(InterpreterFrame* fp);
133 void check(AbstractFramePtr frame);
134 void check(SavedStacks* stacks);
135
check(Handle<JSPropertyDescriptor> desc)136 void check(Handle<JSPropertyDescriptor> desc) {
137 check(desc.object());
138 if (desc.hasGetterObject())
139 check(desc.getterObject());
140 if (desc.hasSetterObject())
141 check(desc.setterObject());
142 check(desc.value());
143 }
144 };
145
146 /*
147 * Don't perform these checks when called from a finalizer. The checking
148 * depends on other objects not having been swept yet.
149 */
150 #define START_ASSERT_SAME_COMPARTMENT() \
151 if (cx->isJSContext() && cx->asJSContext()->runtime()->isHeapBusy()) \
152 return; \
153 CompartmentChecker c(cx)
154
155 template <class T1> inline void
releaseAssertSameCompartment(ExclusiveContext * cx,const T1 & t1)156 releaseAssertSameCompartment(ExclusiveContext* cx, const T1& t1)
157 {
158 START_ASSERT_SAME_COMPARTMENT();
159 c.check(t1);
160 }
161
162 template <class T1> inline void
assertSameCompartment(ExclusiveContext * cx,const T1 & t1)163 assertSameCompartment(ExclusiveContext* cx, const T1& t1)
164 {
165 #ifdef JS_CRASH_DIAGNOSTICS
166 START_ASSERT_SAME_COMPARTMENT();
167 c.check(t1);
168 #endif
169 }
170
171 template <class T1> inline void
assertSameCompartmentDebugOnly(ExclusiveContext * cx,const T1 & t1)172 assertSameCompartmentDebugOnly(ExclusiveContext* cx, const T1& t1)
173 {
174 #if defined(DEBUG) && defined(JS_CRASH_DIAGNOSTICS)
175 START_ASSERT_SAME_COMPARTMENT();
176 c.check(t1);
177 #endif
178 }
179
180 template <class T1, class T2> inline void
assertSameCompartment(ExclusiveContext * cx,const T1 & t1,const T2 & t2)181 assertSameCompartment(ExclusiveContext* cx, const T1& t1, const T2& t2)
182 {
183 #ifdef JS_CRASH_DIAGNOSTICS
184 START_ASSERT_SAME_COMPARTMENT();
185 c.check(t1);
186 c.check(t2);
187 #endif
188 }
189
190 template <class T1, class T2, class T3> inline void
assertSameCompartment(ExclusiveContext * cx,const T1 & t1,const T2 & t2,const T3 & t3)191 assertSameCompartment(ExclusiveContext* cx, const T1& t1, const T2& t2, const T3& t3)
192 {
193 #ifdef JS_CRASH_DIAGNOSTICS
194 START_ASSERT_SAME_COMPARTMENT();
195 c.check(t1);
196 c.check(t2);
197 c.check(t3);
198 #endif
199 }
200
201 template <class T1, class T2, class T3, class T4> inline void
assertSameCompartment(ExclusiveContext * cx,const T1 & t1,const T2 & t2,const T3 & t3,const T4 & t4)202 assertSameCompartment(ExclusiveContext* cx,
203 const T1& t1, const T2& t2, const T3& t3, const T4& t4)
204 {
205 #ifdef JS_CRASH_DIAGNOSTICS
206 START_ASSERT_SAME_COMPARTMENT();
207 c.check(t1);
208 c.check(t2);
209 c.check(t3);
210 c.check(t4);
211 #endif
212 }
213
214 template <class T1, class T2, class T3, class T4, class T5> inline void
assertSameCompartment(ExclusiveContext * cx,const T1 & t1,const T2 & t2,const T3 & t3,const T4 & t4,const T5 & t5)215 assertSameCompartment(ExclusiveContext* cx,
216 const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5)
217 {
218 #ifdef JS_CRASH_DIAGNOSTICS
219 START_ASSERT_SAME_COMPARTMENT();
220 c.check(t1);
221 c.check(t2);
222 c.check(t3);
223 c.check(t4);
224 c.check(t5);
225 #endif
226 }
227
228 #undef START_ASSERT_SAME_COMPARTMENT
229
230 STATIC_PRECONDITION_ASSUME(ubound(args.argv_) >= argc)
231 MOZ_ALWAYS_INLINE bool
CallJSNative(JSContext * cx,Native native,const CallArgs & args)232 CallJSNative(JSContext* cx, Native native, const CallArgs& args)
233 {
234 JS_CHECK_RECURSION(cx, return false);
235
236 #ifdef DEBUG
237 bool alreadyThrowing = cx->isExceptionPending();
238 #endif
239 assertSameCompartment(cx, args);
240 bool ok = native(cx, args.length(), args.base());
241 if (ok) {
242 assertSameCompartment(cx, args.rval());
243 MOZ_ASSERT_IF(!alreadyThrowing, !cx->isExceptionPending());
244 }
245 return ok;
246 }
247
248 STATIC_PRECONDITION_ASSUME(ubound(args.argv_) >= argc)
249 MOZ_ALWAYS_INLINE bool
CallNativeImpl(JSContext * cx,NativeImpl impl,const CallArgs & args)250 CallNativeImpl(JSContext* cx, NativeImpl impl, const CallArgs& args)
251 {
252 #ifdef DEBUG
253 bool alreadyThrowing = cx->isExceptionPending();
254 #endif
255 assertSameCompartment(cx, args);
256 bool ok = impl(cx, args);
257 if (ok) {
258 assertSameCompartment(cx, args.rval());
259 MOZ_ASSERT_IF(!alreadyThrowing, !cx->isExceptionPending());
260 }
261 return ok;
262 }
263
264 STATIC_PRECONDITION(ubound(args.argv_) >= argc)
265 MOZ_ALWAYS_INLINE bool
CallJSNativeConstructor(JSContext * cx,Native native,const CallArgs & args)266 CallJSNativeConstructor(JSContext* cx, Native native, const CallArgs& args)
267 {
268 #ifdef DEBUG
269 RootedObject callee(cx, &args.callee());
270 #endif
271
272 MOZ_ASSERT(args.thisv().isMagic());
273 if (!CallJSNative(cx, native, args))
274 return false;
275
276 /*
277 * Native constructors must return non-primitive values on success.
278 * Although it is legal, if a constructor returns the callee, there is a
279 * 99.9999% chance it is a bug. If any valid code actually wants the
280 * constructor to return the callee, the assertion can be removed or
281 * (another) conjunct can be added to the antecedent.
282 *
283 * Exceptions:
284 *
285 * - Proxies are exceptions to both rules: they can return primitives and
286 * they allow content to return the callee.
287 *
288 * - CallOrConstructBoundFunction is an exception as well because we might
289 * have used bind on a proxy function.
290 *
291 * - new Iterator(x) is user-hookable; it returns x.__iterator__() which
292 * could be any object.
293 *
294 * - (new Object(Object)) returns the callee.
295 */
296 MOZ_ASSERT_IF(native != js::proxy_Construct &&
297 native != js::CallOrConstructBoundFunction &&
298 native != js::IteratorConstructor &&
299 (!callee->is<JSFunction>() || callee->as<JSFunction>().native() != obj_construct),
300 args.rval().isObject() && callee != &args.rval().toObject());
301
302 return true;
303 }
304
305 MOZ_ALWAYS_INLINE bool
CallJSGetterOp(JSContext * cx,GetterOp op,HandleObject obj,HandleId id,MutableHandleValue vp)306 CallJSGetterOp(JSContext* cx, GetterOp op, HandleObject obj, HandleId id,
307 MutableHandleValue vp)
308 {
309 JS_CHECK_RECURSION(cx, return false);
310
311 assertSameCompartment(cx, obj, id, vp);
312 bool ok = op(cx, obj, id, vp);
313 if (ok)
314 assertSameCompartment(cx, vp);
315 return ok;
316 }
317
318 MOZ_ALWAYS_INLINE bool
CallJSSetterOp(JSContext * cx,SetterOp op,HandleObject obj,HandleId id,MutableHandleValue vp,ObjectOpResult & result)319 CallJSSetterOp(JSContext* cx, SetterOp op, HandleObject obj, HandleId id, MutableHandleValue vp,
320 ObjectOpResult& result)
321 {
322 JS_CHECK_RECURSION(cx, return false);
323
324 assertSameCompartment(cx, obj, id, vp);
325 return op(cx, obj, id, vp, result);
326 }
327
328 inline bool
CallJSAddPropertyOp(JSContext * cx,JSAddPropertyOp op,HandleObject obj,HandleId id,HandleValue v)329 CallJSAddPropertyOp(JSContext* cx, JSAddPropertyOp op, HandleObject obj, HandleId id,
330 HandleValue v)
331 {
332 JS_CHECK_RECURSION(cx, return false);
333
334 assertSameCompartment(cx, obj, id, v);
335 return op(cx, obj, id, v);
336 }
337
338 inline bool
CallJSDeletePropertyOp(JSContext * cx,JSDeletePropertyOp op,HandleObject receiver,HandleId id,ObjectOpResult & result)339 CallJSDeletePropertyOp(JSContext* cx, JSDeletePropertyOp op, HandleObject receiver, HandleId id,
340 ObjectOpResult& result)
341 {
342 JS_CHECK_RECURSION(cx, return false);
343
344 assertSameCompartment(cx, receiver, id);
345 if (op)
346 return op(cx, receiver, id, result);
347 return result.succeed();
348 }
349
350 inline uintptr_t
GetNativeStackLimit(ExclusiveContext * cx)351 GetNativeStackLimit(ExclusiveContext* cx)
352 {
353 StackKind kind;
354 if (cx->isJSContext()) {
355 kind = cx->asJSContext()->runningWithTrustedPrincipals()
356 ? StackForTrustedScript : StackForUntrustedScript;
357 } else {
358 // For other threads, we just use the trusted stack depth, since it's
359 // unlikely that we'll be mixing trusted and untrusted code together.
360 kind = StackForTrustedScript;
361 }
362 return cx->perThreadData->nativeStackLimit[kind];
363 }
364
365 inline LifoAlloc&
typeLifoAlloc()366 ExclusiveContext::typeLifoAlloc()
367 {
368 return zone()->types.typeLifoAlloc;
369 }
370
371 } /* namespace js */
372
373 inline void
setPendingException(js::Value v)374 JSContext::setPendingException(js::Value v)
375 {
376 // overRecursed_ is set after the fact by ReportOverRecursed.
377 this->overRecursed_ = false;
378 this->throwing = true;
379 this->unwrappedException_ = v;
380 // We don't use assertSameCompartment here to allow
381 // js::SetPendingExceptionCrossContext to work.
382 MOZ_ASSERT_IF(v.isObject(), v.toObject().compartment() == compartment());
383 }
384
385 inline bool
runningWithTrustedPrincipals()386 JSContext::runningWithTrustedPrincipals() const
387 {
388 return !compartment() || compartment()->principals() == runtime()->trustedPrincipals();
389 }
390
391 inline void
enterCompartment(JSCompartment * c)392 js::ExclusiveContext::enterCompartment(JSCompartment* c)
393 {
394 enterCompartmentDepth_++;
395 c->enter();
396 setCompartment(c);
397 }
398
399 inline void
enterNullCompartment()400 js::ExclusiveContext::enterNullCompartment()
401 {
402 enterCompartmentDepth_++;
403 setCompartment(nullptr);
404 }
405
406 inline void
leaveCompartment(JSCompartment * oldCompartment)407 js::ExclusiveContext::leaveCompartment(JSCompartment* oldCompartment)
408 {
409 MOZ_ASSERT(hasEnteredCompartment());
410 enterCompartmentDepth_--;
411
412 // Only call leave() after we've setCompartment()-ed away from the current
413 // compartment.
414 JSCompartment* startingCompartment = compartment_;
415 setCompartment(oldCompartment);
416 if (startingCompartment)
417 startingCompartment->leave();
418 }
419
420 inline void
setCompartment(JSCompartment * comp)421 js::ExclusiveContext::setCompartment(JSCompartment* comp)
422 {
423 // ExclusiveContexts can only be in the atoms zone or in exclusive zones.
424 MOZ_ASSERT_IF(!isJSContext() && !runtime_->isAtomsCompartment(comp),
425 comp->zone()->usedByExclusiveThread);
426
427 // Normal JSContexts cannot enter exclusive zones.
428 MOZ_ASSERT_IF(isJSContext() && comp,
429 !comp->zone()->usedByExclusiveThread);
430
431 // Only one thread can be in the atoms compartment at a time.
432 MOZ_ASSERT_IF(runtime_->isAtomsCompartment(comp),
433 runtime_->currentThreadHasExclusiveAccess());
434
435 // Make sure that the atoms compartment has its own zone.
436 MOZ_ASSERT_IF(comp && !runtime_->isAtomsCompartment(comp),
437 !comp->zone()->isAtomsZone());
438
439 // Both the current and the new compartment should be properly marked as
440 // entered at this point.
441 MOZ_ASSERT_IF(compartment_, compartment_->hasBeenEntered());
442 MOZ_ASSERT_IF(comp, comp->hasBeenEntered());
443
444 compartment_ = comp;
445 zone_ = comp ? comp->zone() : nullptr;
446 arenas_ = zone_ ? &zone_->arenas : nullptr;
447 }
448
449 inline JSScript*
currentScript(jsbytecode ** ppc,MaybeAllowCrossCompartment allowCrossCompartment)450 JSContext::currentScript(jsbytecode** ppc,
451 MaybeAllowCrossCompartment allowCrossCompartment) const
452 {
453 if (ppc)
454 *ppc = nullptr;
455
456 js::Activation* act = runtime()->activation();
457 while (act && (act->cx() != this || (act->isJit() && !act->asJit()->isActive())))
458 act = act->prev();
459
460 if (!act)
461 return nullptr;
462
463 MOZ_ASSERT(act->cx() == this);
464
465 if (act->isJit()) {
466 JSScript* script = nullptr;
467 js::jit::GetPcScript(const_cast<JSContext*>(this), &script, ppc);
468 if (!allowCrossCompartment && script->compartment() != compartment()) {
469 if (ppc)
470 *ppc = nullptr;
471 return nullptr;
472 }
473 return script;
474 }
475
476 if (act->isAsmJS())
477 return nullptr;
478
479 MOZ_ASSERT(act->isInterpreter());
480
481 js::InterpreterFrame* fp = act->asInterpreter()->current();
482 MOZ_ASSERT(!fp->runningInJit());
483
484 JSScript* script = fp->script();
485 if (!allowCrossCompartment && script->compartment() != compartment())
486 return nullptr;
487
488 if (ppc) {
489 *ppc = act->asInterpreter()->regs().pc;
490 MOZ_ASSERT(script->containsPC(*ppc));
491 }
492 return script;
493 }
494
495 #endif /* jscntxtinlines_h */
496