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