js/src/jscntxtinlines.h

Thu, 15 Jan 2015 15:55:04 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:55:04 +0100
branch
TOR_BUG_9701
changeset 9
a63d609f5ebe
permissions
-rw-r--r--

Back out 97036ab72558 which inappropriately compared turds to third parties.

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

mercurial