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