Bug 1264961 - Fix OOM case in Debugger::replaceFrameGuts. (r=jimb)
authorShu-yu Guo <shu@rfrn.org>
Wed, 20 Apr 2016 14:52:12 -0700
changeset 332048 d4826513cafc09e7bc261b2c777f5047ee2bfdd3
parent 332047 d9ebe0f67883212df579984595f447f655eb332b
child 332049 ae89c24b76920d2203353580091f4a9240b148af
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimb
bugs1264961
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1264961 - Fix OOM case in Debugger::replaceFrameGuts. (r=jimb)
js/src/jit-test/tests/debug/bug1264961.js
js/src/vm/Debugger.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1264961.js
@@ -0,0 +1,28 @@
+// |jit-test| slow;
+
+if (!('oomTest' in this))
+  quit();
+
+loadFile(`
+  var o = {}
+  var global = this;
+  var p = new Proxy(o, {
+    "deleteProperty": function (await , key) {
+      var g = newGlobal();
+      g.parent = global;
+      g.eval("var dbg = new Debugger(parent); dbg.onEnterFrame = function(frame) {};");
+    }
+  })
+  for (var i=0; i<100; i++);
+  assertEq(delete p.foo, true);
+`);
+function loadFile(lfVarx) {
+    var k = 0;
+    oomTest(function() {
+        // In practice a crash occurs before iteration 4000.
+        if (k++ > 4000)
+          quit();
+        eval(lfVarx);
+    })
+}
+
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -5870,16 +5870,24 @@ Debugger::replaceFrameGuts(JSContext* cx
         DebugScopes::forwardLiveFrame(cx, from, to);
     });
 
     // Forward live Debugger.Frame objects.
     Rooted<DebuggerFrameVector> frames(cx, DebuggerFrameVector(cx));
     if (!getDebuggerFrames(from, &frames))
         return false;
 
+    // If during the loop below we hit an OOM, we must also rollback any of
+    // the frames that were successfully replaced. For OSR frames, OOM here
+    // means those frames will pop from the OSR trampoline, which does not
+    // call Debugger::onLeaveFrame.
+    auto removeToDebuggerFramesOnExit = MakeScopeExit([&] {
+        removeFromFrameMapsAndClearBreakpointsIn(cx, to);
+    });
+
     for (size_t i = 0; i < frames.length(); i++) {
         HandleNativeObject frameobj = frames[i];
         Debugger* dbg = Debugger::fromChildJSObject(frameobj);
 
         // Update frame object's ScriptFrameIter::data pointer.
         DebuggerFrame_freeScriptFrameIterData(cx->runtime()->defaultFreeOp(), frameobj);
         ScriptFrameIter::Data* data = iter.copyData();
         if (!data)
@@ -5891,16 +5899,19 @@ Debugger::replaceFrameGuts(JSContext* cx
 
         // Add the frame object with |to| as key.
         if (!dbg->frames.putNew(to, frameobj)) {
             ReportOutOfMemory(cx);
             return false;
         }
     }
 
+    // All frames successfuly replaced, cancel the rollback.
+    removeToDebuggerFramesOnExit.release();
+
     return true;
 }
 
 /* static */ bool
 Debugger::inFrameMaps(AbstractFramePtr frame)
 {
     bool foundAny = false;
     forEachDebuggerFrame(frame, [&](NativeObject* frameobj) { foundAny = true; });