If a Debug.Object’s existence is somehow observable, keep it alive. This means it is alive if it has expandos or is a key of a live WeakMap. Since we have no way of telling when those things are true, simply mark as if each referent had a strong reference back to each corresponding Debug.Object.
authorJason Orendorff <jorendorff@mozilla.com>
Fri, 06 May 2011 11:51:43 -0500
changeset 74796 4af0f2c61f899e432eb9ddb5d05267a5c1dfe729
parent 74795 18e81fe5abac6810c744012e1236b41a5b83dbbd
child 74797 930f17ad5ff1800a545d307b0d5c611950f821a3
push idunknown
push userunknown
push dateunknown
milestone6.0a1
If a Debug.Object’s existence is somehow observable, keep it alive. This means it is alive if it has expandos or is a key of a live WeakMap. Since we have no way of telling when those things are true, simply mark as if each referent had a strong reference back to each corresponding Debug.Object.
js/src/jit-test/tests/debug/gc-03.js
js/src/jit-test/tests/debug/gc-04.js
js/src/jsdbg.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/gc-03.js
@@ -0,0 +1,27 @@
+// |jit-test| debug
+// Storing a property on a Debug.Object protects it from GC as long as the
+// referent is alive.
+
+var g = newGlobal('new-compartment');
+var N = g.N = 3;
+var dbg = Debug(g);
+
+var i = 0;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+        frame.arguments[0].id = i++;
+    }
+};
+g.eval("function f(x) { debugger; }");
+g.eval("var arr = [], j; for (j = 0; j < N; j++) arr[j] = {};");
+g.eval("for (j = 0; j < N; j++) f(arr[j]);");
+assertEq(i, N);
+
+gc(); gc();
+
+i = 0;
+dbg.hooks.debuggerHandler = function (frame) {
+    assertEq(frame.arguments[0].id, i++)
+}
+g.eval("for (j = 0; j < N; j++) f(arr[j]);");
+assertEq(i, N);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/gc-04.js
@@ -0,0 +1,28 @@
+// |jit-test| debug
+// Storing a Debug.Object as a key in a WeakMap protects it from GC as long as
+// the referent is alive.
+
+var g = newGlobal('new-compartment');
+var N = g.N = 10;
+var dbg = Debug(g);
+var cache = new WeakMap;
+
+var i = 0;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+        cache.set(frame.arguments[0], i++);
+    }
+};
+g.eval("function f(x) { debugger; }");
+g.eval("var arr = [], j; for (j = 0; j < N; j++) arr[j] = {};");
+g.eval("for (j = 0; j < N; j++) f(arr[j]);");
+assertEq(i, N);
+
+gc(); gc();
+
+i = 0;
+dbg.hooks.debuggerHandler = function (frame) {
+    assertEq(cache.get(frame.arguments[0]), i++)
+};
+g.eval("for (j = 0; j < N; j++) f(arr[j]);");
+assertEq(i, N);
--- a/js/src/jsdbg.cpp
+++ b/js/src/jsdbg.cpp
@@ -478,18 +478,23 @@ Debug::mark(GCMarker *trc, JSCompartment
                 // Debug.Objects, since the referents might be alive and
                 // therefore the table entries must remain.
                 //
                 // If comp is null, then for each key (referent-wrapper) that
                 // is marked, mark the corresponding value.
                 //
                 if (!comp || obj->compartment() == comp) {
                     for (ObjectMap::Range r = dbg->objects.all(); !r.empty(); r.popFront()) {
-                        if (!r.front().value->isMarked() && (comp || r.front().key->isMarked())) {
-                            MarkObject(trc, *r.front().key, "Debug.Object with live referent");
+                        // The unwrap() call below has the following effect: we
+                        // mark the Debug.Object if the *referent* is alive,
+                        // even if the CCW of the referent seems unreachable.
+                        if (!r.front().value->isMarked() &&
+                            (comp || r.front().key->unwrap()->isMarked()))
+                        {
+                            MarkObject(trc, *r.front().value, "Debug.Object with live referent");
                             markedAny = true;
                         }
                     }
                 }
             }
         }
     }
     return markedAny;