Bug 680428 - onEnterFrame does not work with tracejit. r=dvander.
☠☠ backed out by dff3ecc0451a ☠ ☠
authorJason Orendorff <jorendorff@mozilla.com>
Tue, 23 Aug 2011 14:42:17 -0500
changeset 75762 305a3a0e26fdeb31669d76ee901d84073aadeb00
parent 75761 d898ca543bbb8beb57a9fbb3dc6a07dfefb45908
child 75763 62f8ca0717d690f45d2975ad6a2168fdd88fe139
push id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
reviewersdvander
bugs680428
milestone9.0a1
Bug 680428 - onEnterFrame does not work with tracejit. r=dvander.
js/src/jit-test/tests/debug/onEnterFrame-04.js
js/src/jit-test/tests/debug/onEnterFrame-05.js
js/src/jit-test/tests/debug/onEnterFrame-06.js
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jscompartment.cpp
js/src/jsdbgapi.cpp
copy from js/src/jit-test/tests/debug/onEnterFrame-05.js
copy to js/src/jit-test/tests/debug/onEnterFrame-04.js
--- a/js/src/jit-test/tests/debug/onEnterFrame-05.js
+++ b/js/src/jit-test/tests/debug/onEnterFrame-05.js
@@ -1,17 +1,15 @@
-// We detect and stop the runaway recursion caused by making onEnterFrame a wrapper of a debuggee function.
+// The tracejit does not prevent onEnterFrame from being called.
 
 var g = newGlobal('new-compartment');
-g.n = 0;
-g.eval("function f(frame) { n++; return 42; }");
-print('ok');
-var dbg = Debugger(g);
-dbg.onEnterFrame = g.f;
+g.eval("function f() { return 1; }\n");
+var N = g.N = RUNLOOP + 2;
+g.eval("function h() {\n" +
+       "    for (var i = 0; i < N; i += f()) {}\n" +
+       "}");
+g.h(); // record loop
 
-// Since enterFrame cannot throw, the InternalError is reported and execution proceeds.
-var x = g.f();
-assertEq(x, 42);
-assertEq(g.n > 20, true);
-
-// When an error is reported, the shell usually exits with a nonzero exit
-// code. If we get here, the test passed, so override that behavior.
-quit(0);
+var dbg = Debugger(g);
+var log = '';
+dbg.onEnterFrame = function (frame) { log += frame.callee.name; };
+g.h();
+assertEq(log, 'h' + Array(N + 1).join('f'));
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/onEnterFrame-06.js
@@ -0,0 +1,19 @@
+// The tracejit does not prevent onEnterFrame from being called after entering
+// a debuggee compartment from a non-debuggee compartment.
+
+var g1 = newGlobal('new-compartment');
+var g2 = newGlobal('new-compartment');
+var dbg = Debugger(g1, g2);
+dbg.removeDebuggee(g2); // turn off debug mode in g2
+
+g1.eval("function f() { return 1; }\n");
+var N = g1.N = RUNLOOP + 2;
+g1.eval("function h() {\n" +
+       "    for (var i = 0; i < N; i += f()) {}\n" +
+       "}");
+g1.h(); // record loop
+
+var log = '';
+dbg.onEnterFrame = function (frame) { log += frame.callee.name; };
+g1.h();
+assertEq(log, 'h' + Array(N + 1).join('f'));
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -565,17 +565,17 @@ js_DestroyContext(JSContext *cx, JSDestr
 JSContext *
 js_ContextIterator(JSRuntime *rt, JSBool unlocked, JSContext **iterp)
 {
     JSContext *cx = *iterp;
 
     Maybe<AutoLockGC> lockIf;
     if (unlocked)
         lockIf.construct(rt);
-    cx = js_ContextFromLinkField(cx ? cx->link.next : rt->contextList.next);
+    cx = JSContext::fromLinkField(cx ? cx->link.next : rt->contextList.next);
     if (&cx->link == &rt->contextList)
         cx = NULL;
     *iterp = cx;
     return cx;
 }
 
 JS_FRIEND_API(JSContext *)
 js_NextActiveContext(JSRuntime *rt, JSContext *cx)
@@ -1382,19 +1382,19 @@ JSContext::resetCompartment()
          * can only fail due to bugs in the engine or embedding.)
          */
         OBJ_TO_INNER_OBJECT(this, scopeobj);
         if (!scopeobj)
             goto error;
     }
 
     compartment = scopeobj->compartment();
-
     if (isExceptionPending())
         wrapPendingException();
+    updateJITEnabled();
     return;
 
 error:
 
     /*
      * If we try to use the context without a selected compartment,
      * we will crash.
      */
@@ -1575,16 +1575,18 @@ IsJITBrokenHere()
 #endif
 
 void
 JSContext::updateJITEnabled()
 {
 #ifdef JS_TRACER
     traceJitEnabled = ((runOptions & JSOPTION_JIT) &&
                        !IsJITBrokenHere() &&
+                       compartment &&
+                       !compartment->debugMode() &&
                        (debugHooks == &js_NullDebugHooks ||
                         (debugHooks == &runtime->globalDebugHooks &&
                          !runtime->debuggerInhibitsJIT())));
 #endif
 #ifdef JS_METHODJIT
     methodJitEnabled = (runOptions & JSOPTION_METHODJIT) &&
                        !IsJITBrokenHere()
 # if defined JS_CPU_X86 || defined JS_CPU_X64
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -1139,19 +1139,16 @@ struct JSContext
 
     void setThread(JSThread *thread);
     static const size_t threadOffset() { return offsetof(JSContext, thread_); }
 
     unsigned            outstandingRequests;/* number of JS_BeginRequest calls
                                                without the corresponding
                                                JS_EndRequest. */
     JSCList             threadLinks;        /* JSThread contextList linkage */
-
-#define CX_FROM_THREAD_LINKS(tl) \
-    ((JSContext *)((char *)(tl) - offsetof(JSContext, threadLinks)))
 #endif
 
     /* Stack of thread-stack-allocated GC roots. */
     js::AutoGCRooter   *autoGCRooters;
 
     /* Debug hooks associated with the current context. */
     const JSDebugHooks  *debugHooks;
 
@@ -1320,16 +1317,28 @@ struct JSContext
 #endif
 
     /*
      * See JS_SetTrustedPrincipals in jsapi.h.
      * Note: !cx->compartment is treated as trusted.
      */
     bool runningWithTrustedPrincipals() const;
 
+    static inline JSContext *fromLinkField(JSCList *link) {
+        JS_ASSERT(link);
+        return reinterpret_cast<JSContext *>(uintptr_t(link) - offsetof(JSContext, link));
+    }
+
+#ifdef JS_THREADSAFE
+    static inline JSContext *fromThreadLinks(JSCList *link) {
+        JS_ASSERT(link);
+        return reinterpret_cast<JSContext *>(uintptr_t(link) - offsetof(JSContext, threadLinks));
+    }
+#endif
+
   private:
     /*
      * The allocation code calls the function to indicate either OOM failure
      * when p is null or that a memory pressure counter has reached some
      * threshold when p is not null. The function takes the pointer and not
      * a boolean flag to minimize the amount of code in its inlined callers.
      */
     JS_FRIEND_API(void) checkMallocGCPressure(void *p);
@@ -2132,35 +2141,58 @@ class ThreadDataIter
     ThreadData *threadData() const {
         JS_ASSERT(!done);
         return &runtime->threadData;
     }
 };
 
 #endif  /* !JS_THREADSAFE */
 
+/*
+ * Enumerate all contexts in a runtime that are in the same thread as a given
+ * context.
+ */
+class ThreadContextRange {
+    JSCList *begin;
+    JSCList *end;
+
+public:
+    explicit ThreadContextRange(JSContext *cx) {
+#ifdef JS_THREADSAFE
+        end = &cx->thread()->contextList;
+#else
+        end = &cx->runtime->contextList;
+#endif
+        begin = end->next;
+    }
+
+    bool empty() const { return begin == end; }
+    void popFront() { JS_ASSERT(!empty()); begin = begin->next; }
+
+    JSContext *front() const {
+#ifdef JS_THREADSAFE
+        return JSContext::fromThreadLinks(begin);
+#else
+        return JSContext::fromLinkField(begin);
+#endif
+    }
+};
+
 } /* namespace js */
 
 /*
  * Create and destroy functions for JSContext, which is manually allocated
  * and exclusively owned.
  */
 extern JSContext *
 js_NewContext(JSRuntime *rt, size_t stackChunkSize);
 
 extern void
 js_DestroyContext(JSContext *cx, JSDestroyContextMode mode);
 
-static JS_INLINE JSContext *
-js_ContextFromLinkField(JSCList *link)
-{
-    JS_ASSERT(link);
-    return reinterpret_cast<JSContext *>(uintptr_t(link) - offsetof(JSContext, link));
-}
-
 /*
  * If unlocked, acquire and release rt->gcLock around *iterp update; otherwise
  * the caller must be holding rt->gcLock.
  */
 extern JSContext *
 js_ContextIterator(JSRuntime *rt, JSBool unlocked, JSContext **iterp);
 
 /*
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -662,24 +662,30 @@ JSCompartment::setDebugModeFromC(JSConte
         if (b && onStack) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_NOT_IDLE);
             return false;
         }
     }
 
     debugModeBits = (debugModeBits & ~uintN(DebugFromC)) | (b ? DebugFromC : 0);
     JS_ASSERT(debugMode() == enabledAfter);
-    if (enabledBefore != enabledAfter && !onStack)
+    if (enabledBefore != enabledAfter)
         updateForDebugMode(cx);
     return true;
 }
 
 void
 JSCompartment::updateForDebugMode(JSContext *cx)
 {
+    for (ThreadContextRange r(cx); !r.empty(); r.popFront()) {
+        JSContext *cx = r.front();
+        if (cx->compartment == this) 
+            cx->updateJITEnabled();
+    }
+
 #ifdef JS_METHODJIT
     bool enabled = debugMode();
 
     if (enabled) {
         JS_ASSERT(!hasScriptsOnStack(cx));
     } else if (hasScriptsOnStack(cx)) {
         hasDebugModeCodeToDrop = true;
         return;
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -284,21 +284,21 @@ JS_ClearAllTrapsForCompartment(JSContext
 
 #ifdef JS_TRACER
 static void
 JITInhibitingHookChange(JSRuntime *rt, bool wasInhibited)
 {
     if (wasInhibited) {
         if (!rt->debuggerInhibitsJIT()) {
             for (JSCList *cl = rt->contextList.next; cl != &rt->contextList; cl = cl->next)
-                js_ContextFromLinkField(cl)->updateJITEnabled();
+                JSContext::fromLinkField(cl)->updateJITEnabled();
         }
     } else if (rt->debuggerInhibitsJIT()) {
         for (JSCList *cl = rt->contextList.next; cl != &rt->contextList; cl = cl->next)
-            js_ContextFromLinkField(cl)->traceJitEnabled = false;
+            JSContext::fromLinkField(cl)->traceJitEnabled = false;
     }
 }
 #endif
 
 JS_PUBLIC_API(JSBool)
 JS_SetInterrupt(JSRuntime *rt, JSInterruptHook hook, void *closure)
 {
 #ifdef JS_TRACER