Add Debug.Frame.prototype.eval.
authorJason Orendorff <jorendorff@mozilla.com>
Fri, 06 May 2011 17:47:47 -0500
changeset 74799 903f198d10ee01f7de2a0451db1fbbc2dafae84d
parent 74798 52446c5ddd225c2917b21f0898cc79b38241927e
child 74800 a91b890f5781dd22eb33e49c67827ac5b0a1530d
push idunknown
push userunknown
push dateunknown
milestone6.0a1
Add Debug.Frame.prototype.eval.
js/src/jit-test/tests/debug/Frame-eval-01.js
js/src/jit-test/tests/debug/Frame-eval-02.js
js/src/jit-test/tests/debug/Frame-eval-03.js
js/src/jit-test/tests/debug/Frame-eval-04.js
js/src/jit-test/tests/debug/Frame-eval-05.js
js/src/jit-test/tests/debug/Frame-eval-06.js
js/src/jit-test/tests/debug/Frame-eval-07.js
js/src/jit-test/tests/debug/Frame-eval-08.js
js/src/jit-test/tests/debug/Frame-eval-09.js
js/src/jsdbg.cpp
js/src/jsinterp.cpp
js/src/jsobj.cpp
js/src/jsobj.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-01.js
@@ -0,0 +1,9 @@
+// |jit-test| debug
+// simplest possible test of Debug.Frame.prototype.eval
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+var c;
+dbg.hooks = {debuggerHandler: function (frame) { c = frame.eval("2 + 2"); }};
+g.eval("debugger;");
+assertEq(c.return, 4);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-02.js
@@ -0,0 +1,11 @@
+// |jit-test| debug
+// frame.eval() throws if frame is not live
+
+load(libdir + "asserts.js");
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+var f;
+dbg.hooks = {debuggerHandler: function (frame) { f = frame; }};
+g.eval("debugger;");
+assertThrowsInstanceOf(function () { f.eval("2 + 2"); }, Error);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-03.js
@@ -0,0 +1,22 @@
+// |jit-test| debug
+// Test eval-ing names in a topmost script frame
+
+load(libdir + "asserts.js");
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+var hits = 0;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+        assertEq(frame.eval("a").return, 2);
+        assertEq(frame.eval("c").return, 4);
+        var exc = frame.eval("d").throw;
+        assertEq(exc instanceof Debug.Object, true);
+        assertEq(exc.getPrototype(), frame.eval("ReferenceError.prototype").return);
+        hits++;
+    }
+};
+g.eval("function f(a, b) { var c = a + b; debugger; eval('debugger;'); }");
+g.eval("f(2, 2);");
+g.eval("var a = 2, b = 2, c = a + b; debugger;");
+assertEq(hits, 3);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-04.js
@@ -0,0 +1,14 @@
+// |jit-test| debug
+// frame.eval SyntaxErrors are reflected, not thrown
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+var exc, SEp;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+        exc = frame.eval("#$@!").throw;
+        SEp = frame.eval("SyntaxError.prototype").return;
+    }
+};
+g.eval("debugger;");
+assertEq(exc.getPrototype(), SEp);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-05.js
@@ -0,0 +1,17 @@
+// |jit-test| debug
+// var declarations in strict frame.eval do not modify the frame
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+var cv;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+        cv = frame.eval("'use strict'; var a = 2; h();");
+    }
+};
+g.a = 1;
+g.eval("function f(s) { function h() { return a; } eval(s); debugger; } ");
+g.eval("f('0');");
+assertEq(cv.return, 1);
+g.eval("f('var a = 3;');");
+assertEq(cv.return, 3);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-06.js
@@ -0,0 +1,22 @@
+// |jit-test| debug
+// frame.eval throws if frame is a generator frame that isn't currently on the stack
+
+load(libdir + "asserts.js");
+
+var g = newGlobal('new-compartment');
+g.eval("function gen(a) { debugger; yield a; }");
+g.eval("function test() { debugger; }");
+var dbg = new Debug(g);
+var genframe;
+var hits = 0;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+        if (frame.callee.name == 'gen')
+            genframe = frame;
+        else
+            assertThrowsInstanceOf(function () { genframe.eval("a"); }, Error);
+        hits++;
+    }
+};
+g.eval("var it = gen(42); assertEq(it.next(), 42); test();");
+assertEq(hits, 2);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-07.js
@@ -0,0 +1,34 @@
+// |jit-test| debug
+// test frame.eval in non-top frames
+
+var g = newGlobal('new-compartment');
+var N = g.N = 12; // must be even
+assertEq(N % 2, 0);
+var dbg = new Debug(g);
+var hits = 0;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+        var n = frame.eval("n").return;
+        if (n === 0) {
+            for (var i = 0; i <= N; i++) {
+                assertEq(frame.type, 'call');
+                assertEq(frame.callee.name, i % 2 === 0 ? 'even' : 'odd');
+                assertEq(frame.eval("n").return, i);
+                frame = frame.older;
+            }
+            assertEq(frame.type, 'call');
+            assertEq(frame.callee.name, null);
+            frame = frame.older;
+            assertEq(frame.type, 'eval');
+            hits++;
+        }
+    }
+};
+
+var result = g.eval("(" + function () {
+        function odd(n) { return n > 0 && !even(n - 1); }
+        function even(n) { debugger; return n == 0 || !odd(n - 1); }
+        return even(N);
+    } + ")();");
+assertEq(result, true);
+assertEq(hits, 1);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-08.js
@@ -0,0 +1,26 @@
+// |jit-test| debug
+// The arguments can escape from a function via a debugging hook.
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+
+// capture arguments object and test function
+var args, testfn;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+        args = frame.eval("arguments").return;
+        testfn = frame.eval("test").return;
+    }
+};
+g.eval("function f() { debugger; }");
+g.eval("var test = " + function test(args) {
+        assertEq(args.length, 3);
+        assertEq(args[0], this);
+        assertEq(args[1], f);
+        assertEq(args[2].toString(), "[object Object]");
+        return 42;
+    } + ";");
+g.eval("f(this, f, {});");
+
+var cv = testfn.apply(null, [args]);
+assertEq(cv.return, 42);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-09.js
@@ -0,0 +1,23 @@
+// |jit-test| debug
+// assigning to local variables in frame.eval code
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+        frame.eval("outerarg = 1; outervar = 2; innerarg = 3; innervar = 4;");
+    }
+};
+
+var result = g.eval("(" + function outer(outerarg) {
+        var outervar = 200;
+        function inner(innerarg) {
+            var innervar = 400;
+            debugger;
+            return innerarg + innervar;
+        }
+        var innersum = inner(300);
+        return outerarg + outervar + innersum;
+    } + ")(100)");
+
+assertEq(result, 10);
--- a/js/src/jsdbg.cpp
+++ b/js/src/jsdbg.cpp
@@ -828,16 +828,42 @@ DebugFrame_getLive(JSContext *cx, uintN 
     if (!thisobj)
         return false;
     StackFrame *fp = (StackFrame *) thisobj->getPrivate();
     vp->setBoolean(!!fp);
     return true;
 }
 
 static JSBool
+DebugFrame_eval(JSContext *cx, uintN argc, Value *vp)
+{
+    REQUIRE_ARGC("Debug.Frame.eval", 1);
+    THIS_FRAME(cx, vp, "eval", thisobj, fp);
+    Debug *dbg = Debug::fromChildJSObject(&vp[1].toObject());
+
+    if (!vp[2].isString()) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_EXPECTED_TYPE,
+                             "Debug.Frame.eval", "string", InformalValueTypeName(vp[2]));
+        return false;
+    }
+    JSLinearString *linearStr = vp[2].toString()->ensureLinear(cx);
+    if (!linearStr)
+        return false;
+    JS::Anchor<JSString *> anchor(linearStr);
+
+    AutoCompartment ac(cx, &fp->scopeChain());
+    if (!ac.enter())
+        return false;
+    Value rval;
+    bool ok = JS_EvaluateUCInStackFrame(cx, Jsvalify(fp), linearStr->chars(), linearStr->length(),
+                                        "debugger eval code", 1, Jsvalify(&rval));
+    return dbg->newCompletionValue(ac, ok, rval, vp);
+}
+
+static JSBool
 DebugFrame_construct(JSContext *cx, uintN argc, Value *vp)
 {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NO_CONSTRUCTOR, "Debug.Frame");
     return false;
 }
 
 static JSPropertySpec DebugFrame_properties[] = {
     JS_PSG("type", DebugFrame_getType, 0),
@@ -845,16 +871,21 @@ static JSPropertySpec DebugFrame_propert
     JS_PSG("older", DebugFrame_getOlder, 0),
     JS_PSG("live", DebugFrame_getLive, 0),
     JS_PSG("callee", DebugFrame_getCallee, 0),
     JS_PSG("generator", DebugFrame_getGenerator, 0),
     JS_PSG("arguments", DebugFrame_getArguments, 0),
     JS_PS_END
 };
 
+static JSFunctionSpec DebugFrame_methods[] = {
+    JS_FN("eval", DebugFrame_eval, 1, 0),
+    JS_FS_END
+};
+
 // === Debug.Object
 
 Class DebugObject_class = {
     "Object", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGOBJECT_COUNT),
     PropertyStub, PropertyStub, PropertyStub, StrictPropertyStub,
     EnumerateStub, ResolveStub, ConvertStub, FinalizeStub,
 };
 
@@ -1018,17 +1049,18 @@ JS_DefineDebugObject(JSContext *cx, JSOb
     JSObject *debugProto = js_InitClass(cx, obj, objProto, &Debug::jsclass, Debug::construct, 1,
                                         Debug::properties, NULL, NULL, NULL, &debugCtor);
     if (!debugProto || !debugProto->ensureClassReservedSlots(cx))
         return false;
 
     JSObject *frameCtor;
     JSObject *frameProto = js_InitClass(cx, debugCtor, objProto, &DebugFrame_class,
                                         DebugFrame_construct, 0,
-                                        DebugFrame_properties, NULL, NULL, NULL, &frameCtor);
+                                        DebugFrame_properties, DebugFrame_methods, NULL, NULL,
+                                        &frameCtor);
     if (!frameProto)
         return false;
 
     JSObject *objectProto = js_InitClass(cx, debugCtor, objProto, &DebugObject_class,
                                          DebugObject_construct, 0,
                                          DebugObject_properties, DebugObject_methods, NULL, NULL);
     if (!objectProto)
         return false;
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -401,33 +401,20 @@ ReportIncompatibleMethod(JSContext *cx, 
     } else if (thisv.isBoolean()) {
         JS_ASSERT(clasp != &js_BooleanClass);
     } else {
         JS_ASSERT(thisv.isUndefined() || thisv.isNull());
     }
 #endif
 
     if (JSFunction *fun = js_ValueToFunction(cx, &vp[0], 0)) {
-        const char *name = thisv.isObject()
-                           ? thisv.toObject().getClass()->name
-                           : thisv.isString()
-                           ? "string"
-                           : thisv.isNumber()
-                           ? "number"
-                           : thisv.isBoolean()
-                           ? "boolean"
-                           : thisv.isNull()
-                           ? js_null_str
-                           : thisv.isUndefined()
-                           ? js_undefined_str
-                           : "value";
         JSAutoByteString funNameBytes;
         if (const char *funName = GetFunctionNameBytes(cx, fun, &funNameBytes)) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
-                                 clasp->name, funName, name);
+                                 clasp->name, funName, InformalValueTypeName(thisv));
         }
     }
 }
 
 /*
  * ECMA requires "the global object", but in embeddings such as the browser,
  * which have multiple top-level objects (windows, frames, etc. in the DOM),
  * we prefer fun's parent.  An example that causes this code to run:
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -854,16 +854,28 @@ NonNullObject(JSContext *cx, const Value
 {
     if (v.isPrimitive()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_NONNULL_OBJECT);
         return NULL;
     }
     return &v.toObject();
 }
 
+const char *
+InformalValueTypeName(const Value &v)
+{
+    return v.isObject() ? v.toObject().getClass()->name :
+           v.isString() ? "string" :
+           v.isNumber() ? "number" :
+           v.isBoolean() ? "boolean" :
+           v.isNull() ? "null" :
+           v.isUndefined() ? "undefined" :
+           "value";
+}
+
 }
 
 /* ES5 15.2.4.2.  Note steps 1 and 2 are errata. */
 static JSBool
 obj_toString(JSContext *cx, uintN argc, Value *vp)
 {
     Value &thisv = vp[1];
 
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1952,11 +1952,13 @@ IsAnyBuiltinEval(JSFunction *fun);
 
 /* 'call' should be for the eval/Function native invocation. */
 extern JSPrincipals *
 PrincipalsForCompiledCode(const CallArgs &call, JSContext *cx);
 
 extern JSObject *
 NonNullObject(JSContext *cx, const Value &v);
 
+extern const char *
+InformalValueTypeName(const Value &v);
 }
 
 #endif /* jsobj_h___ */