Bug 837723: Implement Debugger.Object.prototype.unsafeDereference. r=jorendorff
authorJim Blandy <jimb@mozilla.com>
Mon, 08 Apr 2013 13:14:39 -0700
changeset 128117 07aae4e9a66ae9b1fb32eb5077487ab4cb83d3c2
parent 128116 b65e3791e401ebfd8bb1ce5be1b707f789f9d3fe
child 128118 2fdd3d16ed3b0984e940b87c21203e7ca569eb56
push id24521
push userryanvm@gmail.com
push dateTue, 09 Apr 2013 18:31:04 +0000
treeherdermozilla-central@9d5f05a6d497 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs837723
milestone23.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 837723: Implement Debugger.Object.prototype.unsafeDereference. r=jorendorff
js/src/jit-test/tests/debug/Object-unsafeDereference-01.js
js/src/vm/Debugger.cpp
toolkit/devtools/debugger/tests/mochitest/Makefile.in
toolkit/devtools/debugger/tests/mochitest/moz.build
toolkit/devtools/debugger/tests/mochitest/nonchrome_unsafeDereference.html
toolkit/devtools/debugger/tests/mochitest/test_unsafeDereference.html
toolkit/devtools/debugger/tests/moz.build
toolkit/devtools/debugger/tests/unit/test_unsafeDereference.js
toolkit/devtools/debugger/tests/unit/xpcshell.ini
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-unsafeDereference-01.js
@@ -0,0 +1,10 @@
+// Debugger.Object.prototype.unsafeDereference returns the referent directly.
+
+var g = newGlobal();
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+assertEq(gw.getOwnPropertyDescriptor('Math').value.unsafeDereference(), g.Math);
+
+g.eval('var obj = {}');
+assertEq(gw.getOwnPropertyDescriptor('obj').value.unsafeDereference(), g.obj);
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -4774,16 +4774,24 @@ DebuggerObject_unwrap(JSContext *cx, uns
     }
 
     args.rval().setObject(*unwrapped);
     if (!dbg->wrapDebuggeeValue(cx, args.rval()))
         return false;
     return true;
 }
 
+static JSBool
+DebuggerObject_unsafeDereference(JSContext *cx, unsigned argc, Value *vp)
+{
+    THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "unsafeDereference", args, referent);
+    args.rval().setObject(*referent);
+    return cx->compartment->wrap(cx, args.rval());
+}
+
 static JSPropertySpec DebuggerObject_properties[] = {
     JS_PSG("proto", DebuggerObject_getProto, 0),
     JS_PSG("class", DebuggerObject_getClass, 0),
     JS_PSG("callable", DebuggerObject_getCallable, 0),
     JS_PSG("name", DebuggerObject_getName, 0),
     JS_PSG("displayName", DebuggerObject_getDisplayName, 0),
     JS_PSG("parameterNames", DebuggerObject_getParameterNames, 0),
     JS_PSG("script", DebuggerObject_getScript, 0),
@@ -4805,16 +4813,17 @@ static JSFunctionSpec DebuggerObject_met
     JS_FN("isFrozen", DebuggerObject_isFrozen, 0, 0),
     JS_FN("isExtensible", DebuggerObject_isExtensible, 0, 0),
     JS_FN("apply", DebuggerObject_apply, 0, 0),
     JS_FN("call", DebuggerObject_call, 0, 0),
     JS_FN("makeDebuggeeValue", DebuggerObject_makeDebuggeeValue, 1, 0),
     JS_FN("evalInGlobal", DebuggerObject_evalInGlobal, 1, 0),
     JS_FN("evalInGlobalWithBindings", DebuggerObject_evalInGlobalWithBindings, 2, 0),
     JS_FN("unwrap", DebuggerObject_unwrap, 0, 0),
+    JS_FN("unsafeDereference", DebuggerObject_unsafeDereference, 0, 0),
     JS_FS_END
 };
 
 
 /*** Debugger.Environment ************************************************************************/
 
 static void
 DebuggerEnv_trace(JSTracer *trc, RawObject obj)
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/mochitest/Makefile.in
@@ -0,0 +1,19 @@
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH		= @DEPTH@
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir	= @relativesrcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MOCHITEST_CHROME_FILES	= \
+	test_unsafeDereference.html \
+	nonchrome_unsafeDereference.html \
+	$(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/mochitest/moz.build
@@ -0,0 +1,5 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/mochitest/nonchrome_unsafeDereference.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<script>
+var xhr = new XMLHttpRequest;
+xhr.timeout = 1742;
+xhr.expando = 'Expando!';
+</script>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/mochitest/test_unsafeDereference.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=837723
+
+When we use Debugger.Object.prototype.unsafeDereference to get a non-D.O
+reference to a content object in chrome, that reference should be via an
+xray wrapper.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Mozilla Bug 837723</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+window.onload = function () {
+  SimpleTest.waitForExplicitFinish();
+
+  var iframe = document.createElement("iframe");
+  iframe.src = "http://mochi.test:8888/chrome/toolkit/devtools/debugger/tests/mochitest/nonchrome_unsafeDereference.html";
+
+  iframe.onload = function () {
+    var dbg = new Debugger;
+    var contentDO = dbg.addDebuggee(iframe.contentWindow);
+    var xhrDesc = contentDO.getOwnPropertyDescriptor('xhr');
+
+    isnot(xhrDesc, undefined, "xhr should be visible as property of content global");
+    isnot(xhrDesc.value, undefined, "xhr should have a value");
+
+    var xhr = xhrDesc.value.unsafeDereference();
+
+    is(typeof xhr, "object", "we should be able to deference xhr's value's D.O");
+    is(xhr.timeout, 1742, "chrome should see the xhr's 'timeout' property");
+    is(xhr.expando, undefined, "chrome should not see the xhr's 'expando' property");
+
+    SimpleTest.finish();
+  }
+
+  document.body.appendChild(iframe);
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/toolkit/devtools/debugger/tests/moz.build
+++ b/toolkit/devtools/debugger/tests/moz.build
@@ -1,8 +1,9 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+DIRS += ['mochitest']
+
 MODULE = 'test_debugger'
-
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/unit/test_unsafeDereference.js
@@ -0,0 +1,134 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+// Test Debugger.Object.prototype.unsafeDereference in the presence of
+// interesting cross-compartment wrappers.
+//
+// This is not really a debugger server test; it's more of a Debugger test.
+// But we need xpcshell and Components.utils.Sandbox to get
+// cross-compartment wrappers with interesting properties, and this is the
+// xpcshell test directory most closely related to the JS Debugger API.
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+// Add a method to Debugger.Object for fetching value properties
+// conveniently.
+Debugger.Object.prototype.getProperty = function (aName) {
+  let desc = this.getOwnPropertyDescriptor(aName);
+  if (!desc)
+    return undefined;
+  if (!desc.value) {
+    throw Error("Debugger.Object.prototype.getProperty: " +
+                "not a value property: " + aName);
+  }
+  return desc.value;
+};
+
+function run_test() {
+  // Create a low-privilege sandbox, and a chrome-privilege sandbox.
+  let contentBox = Components.utils.Sandbox('http://www.example.com');
+  let chromeBox = Components.utils.Sandbox(this);
+
+  // Create an objects in this compartment, and one in each sandbox. We'll
+  // refer to the objects as "mainObj", "contentObj", and "chromeObj", in
+  // variable and property names.
+  var mainObj = { name: "mainObj" };
+  Components.utils.evalInSandbox('var contentObj = { name: "contentObj" };',
+                                 contentBox);
+  Components.utils.evalInSandbox('var chromeObj = { name: "chromeObj" };',
+                                 chromeBox);
+
+  // Give each global a pointer to all the other globals' objects.
+  contentBox.mainObj = chromeBox.mainObj = mainObj;
+  var contentObj = chromeBox.contentObj = contentBox.contentObj;
+  var chromeObj  = contentBox.chromeObj = chromeBox.chromeObj;
+
+  // First, a whole bunch of basic sanity checks, to ensure that JavaScript
+  // evaluated in various scopes really does see the world the way this
+  // test expects it to.
+
+  // The objects appear as global variables in the sandbox, and as
+  // the sandbox object's properties in chrome.
+  do_check_true(Components.utils.evalInSandbox('mainObj', contentBox)
+                === contentBox.mainObj);
+  do_check_true(Components.utils.evalInSandbox('contentObj', contentBox)
+                === contentBox.contentObj);
+  do_check_true(Components.utils.evalInSandbox('chromeObj', contentBox)
+                === contentBox.chromeObj);
+  do_check_true(Components.utils.evalInSandbox('mainObj', chromeBox)
+                === chromeBox.mainObj);
+  do_check_true(Components.utils.evalInSandbox('contentObj', chromeBox)
+                === chromeBox.contentObj);
+  do_check_true(Components.utils.evalInSandbox('chromeObj', chromeBox)
+                === chromeBox.chromeObj);
+
+  // We (the main global) can see properties of all objects in all globals.
+  do_check_true(contentBox.mainObj.name === "mainObj");
+  do_check_true(contentBox.contentObj.name === "contentObj");
+  do_check_true(contentBox.chromeObj.name === "chromeObj");
+
+  // chromeBox can see properties of all objects in all globals.
+  do_check_eq(Components.utils.evalInSandbox('mainObj.name', chromeBox),
+              'mainObj');
+  do_check_eq(Components.utils.evalInSandbox('contentObj.name', chromeBox),
+              'contentObj');
+  do_check_eq(Components.utils.evalInSandbox('chromeObj.name', chromeBox),
+              'chromeObj');
+
+  // contentBox can see properties of the content object, but not of either
+  // chrome object, because by default, content -> chrome wrappers hide all
+  // object properties.
+  do_check_eq(Components.utils.evalInSandbox('mainObj.name', contentBox),
+              undefined);
+  do_check_eq(Components.utils.evalInSandbox('contentObj.name', contentBox),
+              'contentObj');
+  do_check_eq(Components.utils.evalInSandbox('chromeObj.name', contentBox),
+              undefined);
+
+  // When viewing an object in compartment A from the vantage point of
+  // compartment B, Debugger should give the same results as debuggee code
+  // would.
+
+  // Create a debugger, debugging our two sandboxes.
+  let dbg = new Debugger;
+
+  // Create Debugger.Object instances referring to the two sandboxes, as
+  // seen from their own compartments.
+  let contentBoxDO = dbg.addDebuggee(contentBox);
+  let chromeBoxDO = dbg.addDebuggee(chromeBox);
+
+  // Use Debugger to view the objects from contentBox. We should get the
+  // same D.O instance from both getProperty and makeDebuggeeValue, and the
+  // same property visibility we checked for above.
+  let mainFromContentDO = contentBoxDO.getProperty('mainObj');
+  do_check_eq(mainFromContentDO, contentBoxDO.makeDebuggeeValue(mainObj));
+  do_check_eq(mainFromContentDO.getProperty('name'), undefined);
+  do_check_eq(mainFromContentDO.unsafeDereference(), mainObj);
+
+  let contentFromContentDO = contentBoxDO.getProperty('contentObj');
+  do_check_eq(contentFromContentDO, contentBoxDO.makeDebuggeeValue(contentObj));
+  do_check_eq(contentFromContentDO.getProperty('name'), 'contentObj');
+  do_check_eq(contentFromContentDO.unsafeDereference(), contentObj);
+
+  let chromeFromContentDO = contentBoxDO.getProperty('chromeObj');
+  do_check_eq(chromeFromContentDO, contentBoxDO.makeDebuggeeValue(chromeObj));
+  do_check_eq(chromeFromContentDO.getProperty('name'), undefined);
+  do_check_eq(chromeFromContentDO.unsafeDereference(), chromeObj);
+
+  // Similarly, viewing from chromeBox.
+  let mainFromChromeDO = chromeBoxDO.getProperty('mainObj');
+  do_check_eq(mainFromChromeDO, chromeBoxDO.makeDebuggeeValue(mainObj));
+  do_check_eq(mainFromChromeDO.getProperty('name'), 'mainObj');
+  do_check_eq(mainFromChromeDO.unsafeDereference(), mainObj);
+
+  let contentFromChromeDO = chromeBoxDO.getProperty('contentObj');
+  do_check_eq(contentFromChromeDO, chromeBoxDO.makeDebuggeeValue(contentObj));
+  do_check_eq(contentFromChromeDO.getProperty('name'), 'contentObj');
+  do_check_eq(contentFromChromeDO.unsafeDereference(), contentObj);
+
+  let chromeFromChromeDO = chromeBoxDO.getProperty('chromeObj');
+  do_check_eq(chromeFromChromeDO, chromeBoxDO.makeDebuggeeValue(chromeObj));
+  do_check_eq(chromeFromChromeDO.getProperty('name'), 'chromeObj');
+  do_check_eq(chromeFromChromeDO.unsafeDereference(), chromeObj);
+}
--- a/toolkit/devtools/debugger/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/debugger/tests/unit/xpcshell.ini
@@ -104,8 +104,9 @@ reason = bug 820380
 [test_longstringgrips-02.js]
 [test_source-01.js]
 skip-if = toolkit == "gonk"
 reason = bug 820380
 [test_breakpointstore.js]
 [test_profiler_actor.js]
 skip-if = toolkit == "gonk"
 reason = bug 820380
+[test_unsafeDereference.js]