Bug 1380498 - Add isGeneratorFunction and isAsyncFunction to Debugger.Script and Debugger.Object. r=tromey
authorJim Blandy <jimb@mozilla.com>
Tue, 18 Jul 2017 17:51:44 -0700
changeset 420298 aed855892bc947c5942ea7e92cbf366d27678a86
parent 420297 90b3cc6e01916223335ece76b9d94065d0412dda
child 420299 c8f400fc23a72fab985d3baa08b5cc22d2190326
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstromey
bugs1380498
milestone56.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 1380498 - Add isGeneratorFunction and isAsyncFunction to Debugger.Script and Debugger.Object. r=tromey
js/src/doc/Debugger/Debugger.Object.md
js/src/doc/Debugger/Debugger.Script.md
js/src/jit-test/tests/debug/isAsyncFunction-isGeneratorFunction.js
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
--- a/js/src/doc/Debugger/Debugger.Object.md
+++ b/js/src/doc/Debugger/Debugger.Object.md
@@ -170,16 +170,32 @@ from its prototype:
     bound function; `false` otherwise. If the referent is not a debuggee
     function, or not a function at all, returns `undefined` instead.
 
 `isArrowFunction`
 :   If the referent is a debuggee function, returns `true` if the referent is an
     arrow function; `false` otherwise. If the referent is not a debuggee
     function, or not a function at all, returns `undefined` instead.
 
+`isGeneratorFunction`
+:   If the referent is a debuggee function, returns `true` if the referent was
+    created with a `function*` expression or statement, or false if it is some
+    other sort of function. If the referent is not a debuggee function, or not a
+    function at all, this is `undefined`. (This is always equal to
+    `obj.script.isGeneratorFunction`, assuming `obj.script` is a
+    `Debugger.Script`.)
+
+`isAsyncFunction`
+:   If the referent is a debuggee function, returns `true` if the referent is an
+    async function, defined with an `async function` expression or statement, or
+    false if it is some other sort of function. If the referent is not a
+    debuggee function, or not a function at all, this is `undefined`. (This is
+    always equal to `obj.script.isAsyncFunction`, assuming `obj.script` is a
+    `Debugger.Script`.)
+
 `isPromise`
 :   `true` if the referent is a Promise; `false` otherwise.
 
 `boundTargetFunction`
 :   If the referent is a bound debuggee function, this is its target function—
     the function that was bound to a particular `this` object. If the referent
     is either not a bound function, not a debuggee function, or not a function
     at all, this is `undefined`.
--- a/js/src/doc/Debugger/Debugger.Script.md
+++ b/js/src/doc/Debugger/Debugger.Script.md
@@ -80,16 +80,24 @@ headed by "**if the instance refers to a
 refers to WebAssembly code**", respectively. If the behavior does not differ,
 no such emphasized headings will appear.
 
 ## Accessor Properties of the Debugger.Script Prototype Object
 
 A `Debugger.Script` instance inherits the following accessor properties
 from its prototype:
 
+`isGeneratorFunction`
+:   True if this instance refers to a `JSScript` for a function defined with a
+    `function*` expression or statement. False otherwise.
+
+`isAsyncFunction`
+:   True if this instance refers to a `JSScript` for an async function, defined
+    with an `async function` expression or statement. False otherwise.
+
 `displayName`
 :   **If the instance refers to a `JSScript`**, this is the script's display
     name, if it has one. If the script has no display name &mdash; for example,
     if it is a top-level `eval` script &mdash; this is `undefined`.
 
     If the script's function has a given name, its display name is the same as
     its function's given name.
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/isAsyncFunction-isGeneratorFunction.js
@@ -0,0 +1,54 @@
+// Debugger.Script.prototype.isAsyncFunction, Debugger.Object.prototype.isAsyncFunction,
+// Debugger.Script.prototype.isGeneratorFunction, Debugger.Object.prototype.isGeneratorFunction
+
+var g = newGlobal();
+var dbg = new Debugger();
+var gDO = dbg.addDebuggee(g);
+g.non_debuggee = function non_debuggee () {}
+
+function checkExpr(expr, { isAsync, isGenerator })
+{
+  print("Evaluating: " + uneval(expr));
+  let fn = gDO.executeInGlobal(expr).return;
+
+  assertEq(fn.isAsyncFunction, isAsync);
+  assertEq(fn.isGeneratorFunction, isGenerator);
+
+  // The Debugger.Object and its Debugger.Script should always agree.
+  if (fn.script) {
+    assertEq(fn.isAsyncFunction, fn.script.isAsyncFunction);
+    assertEq(fn.isGeneratorFunction, fn.script.isGeneratorFunction);
+  }
+}
+
+checkExpr('({})', { isAsync: undefined, isGenerator: undefined });
+checkExpr('non_debuggee', { isAsync: undefined, isGenerator: undefined });
+checkExpr('(function(){})', { isAsync: false, isGenerator: false });
+checkExpr('(function*(){})', { isAsync: false, isGenerator: true });
+checkExpr('(async function snerf(){})', { isAsync: true, isGenerator: false });
+checkExpr('(async function* omlu(){})', { isAsync: true, isGenerator: true });
+
+checkExpr('new Function("1+2")', { isAsync: false, isGenerator: false });
+checkExpr('Object.getPrototypeOf(function*(){}).constructor("1+2")',
+          { isAsync: false, isGenerator: true });
+checkExpr('Object.getPrototypeOf(async function(){}).constructor("1+2")',
+          { isAsync: true, isGenerator: false });
+checkExpr('Object.getPrototypeOf(async function*(){}).constructor("1+2")',
+          { isAsync: true, isGenerator: true });
+
+// Check eval scripts.
+function checkFrame(expr, type)
+{
+  var log = '';
+  dbg.onDebuggerStatement = function(frame) {
+    log += 'd';
+    assertEq(frame.type, type);
+    assertEq(frame.script.isAsyncFunction, false);
+    assertEq(frame.script.isGeneratorFunction, false);
+  }
+  gDO.executeInGlobal(expr);
+  assertEq(log, 'd');
+}
+
+checkFrame('debugger;', 'global');
+checkFrame('eval("debugger;")', 'eval');
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -5648,16 +5648,32 @@ DebuggerScript_checkThis(JSContext* cx, 
     CallArgs args = CallArgsFromVp(argc, vp);                                       \
     RootedObject obj(cx, DebuggerScript_checkThis<JSScript*>(cx, args, fnname,      \
                                                              "a JS script"));       \
     if (!obj)                                                                       \
         return false;                                                               \
     RootedScript script(cx, GetScriptReferent(obj).as<JSScript*>())
 
 static bool
+DebuggerScript_getIsGeneratorFunction(JSContext* cx, unsigned argc, Value* vp)
+{
+    THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get isGeneratorFunction)", args, obj, script);
+    args.rval().setBoolean(script->isLegacyGenerator() || script->isStarGenerator());
+    return true;
+}
+
+static bool
+DebuggerScript_getIsAsyncFunction(JSContext* cx, unsigned argc, Value* vp)
+{
+    THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get isAsyncFunction)", args, obj, script);
+    args.rval().setBoolean(script->isAsync());
+    return true;
+}
+
+static bool
 DebuggerScript_getDisplayName(JSContext* cx, unsigned argc, Value* vp)
 {
     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get displayName)", args, obj, script);
     Debugger* dbg = Debugger::fromChildJSObject(obj);
 
     JSFunction* func = script->functionNonDelazifying();
     JSString* name = func ? func->displayAtom() : nullptr;
     if (!name) {
@@ -7115,16 +7131,18 @@ static bool
 DebuggerScript_construct(JSContext* cx, unsigned argc, Value* vp)
 {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
                               "Debugger.Script");
     return false;
 }
 
 static const JSPropertySpec DebuggerScript_properties[] = {
+    JS_PSG("isGeneratorFunction", DebuggerScript_getIsGeneratorFunction, 0),
+    JS_PSG("isAsyncFunction", DebuggerScript_getIsAsyncFunction, 0),
     JS_PSG("displayName", DebuggerScript_getDisplayName, 0),
     JS_PSG("url", DebuggerScript_getUrl, 0),
     JS_PSG("startLine", DebuggerScript_getStartLine, 0),
     JS_PSG("lineCount", DebuggerScript_getLineCount, 0),
     JS_PSG("source", DebuggerScript_getSource, 0),
     JS_PSG("sourceStart", DebuggerScript_getSourceStart, 0),
     JS_PSG("sourceLength", DebuggerScript_getSourceLength, 0),
     JS_PSG("global", DebuggerScript_getGlobal, 0),
@@ -9169,16 +9187,44 @@ DebuggerObject::isArrowFunctionGetter(JS
         return true;
     }
 
     args.rval().setBoolean(object->isArrowFunction());
     return true;
 }
 
 /* static */ bool
+DebuggerObject::isAsyncFunctionGetter(JSContext* cx, unsigned argc, Value* vp)
+{
+    THIS_DEBUGOBJECT(cx, argc, vp, "get isAsyncFunction", args, object)
+
+    if (!object->isDebuggeeFunction()) {
+        args.rval().setUndefined();
+        return true;
+    }
+
+    args.rval().setBoolean(object->isAsyncFunction());
+    return true;
+}
+
+/* static */ bool
+DebuggerObject::isGeneratorFunctionGetter(JSContext* cx, unsigned argc, Value* vp)
+{
+    THIS_DEBUGOBJECT(cx, argc, vp, "get isGeneratorFunction", args, object)
+
+    if (!object->isDebuggeeFunction()) {
+        args.rval().setUndefined();
+        return true;
+    }
+
+    args.rval().setBoolean(object->isGeneratorFunction());
+    return true;
+}
+
+/* static */ bool
 DebuggerObject::protoGetter(JSContext* cx, unsigned argc, Value* vp)
 {
     THIS_DEBUGOBJECT(cx, argc, vp, "get proto", args, object)
 
     RootedDebuggerObject result(cx);
     if (!DebuggerObject::getPrototypeOf(cx, object, &result))
         return false;
 
@@ -10080,16 +10126,18 @@ DebuggerObject::unwrapMethod(JSContext* 
     args.rval().setObjectOrNull(result);
     return true;
 }
 
 const JSPropertySpec DebuggerObject::properties_[] = {
     JS_PSG("callable", DebuggerObject::callableGetter, 0),
     JS_PSG("isBoundFunction", DebuggerObject::isBoundFunctionGetter, 0),
     JS_PSG("isArrowFunction", DebuggerObject::isArrowFunctionGetter, 0),
+    JS_PSG("isGeneratorFunction", DebuggerObject::isGeneratorFunctionGetter, 0),
+    JS_PSG("isAsyncFunction", DebuggerObject::isAsyncFunctionGetter, 0),
     JS_PSG("proto", DebuggerObject::protoGetter, 0),
     JS_PSG("class", DebuggerObject::classGetter, 0),
     JS_PSG("name", DebuggerObject::nameGetter, 0),
     JS_PSG("displayName", DebuggerObject::displayNameGetter, 0),
     JS_PSG("parameterNames", DebuggerObject::parameterNamesGetter, 0),
     JS_PSG("script", DebuggerObject::scriptGetter, 0),
     JS_PSG("environment", DebuggerObject::environmentGetter, 0),
     JS_PSG("boundTargetFunction", DebuggerObject::boundTargetFunctionGetter, 0),
@@ -10212,16 +10260,33 @@ bool
 DebuggerObject::isArrowFunction() const
 {
     MOZ_ASSERT(isDebuggeeFunction());
 
     return RemoveAsyncWrapper(&referent()->as<JSFunction>())->isArrow();
 }
 
 bool
+DebuggerObject::isAsyncFunction() const
+{
+    MOZ_ASSERT(isDebuggeeFunction());
+
+    return RemoveAsyncWrapper(&referent()->as<JSFunction>())->isAsync();
+}
+
+bool
+DebuggerObject::isGeneratorFunction() const
+{
+    MOZ_ASSERT(isDebuggeeFunction());
+
+    JSFunction* fun = RemoveAsyncWrapper(&referent()->as<JSFunction>());
+    return fun->isLegacyGenerator() || fun->isStarGenerator();
+}
+
+bool
 DebuggerObject::isGlobal() const
 {
     return referent()->is<GlobalObject>();
 }
 
 bool
 DebuggerObject::isScriptedProxy() const
 {
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -1475,16 +1475,18 @@ class DebuggerObject : public NativeObje
                                     MutableHandleDebuggerObject result);
 
     // Infallible properties
     bool isCallable() const;
     bool isFunction() const;
     bool isDebuggeeFunction() const;
     bool isBoundFunction() const;
     bool isArrowFunction() const;
+    bool isAsyncFunction() const;
+    bool isGeneratorFunction() const;
     bool isGlobal() const;
     bool isScriptedProxy() const;
     bool isPromise() const;
     JSAtom* name(JSContext* cx) const;
     JSAtom* displayName(JSContext* cx) const;
     JS::PromiseState promiseState() const;
     double promiseLifetime() const;
     double promiseTimeToResolution() const;
@@ -1514,16 +1516,18 @@ class DebuggerObject : public NativeObje
     static MOZ_MUST_USE bool requireGlobal(JSContext* cx, HandleDebuggerObject object);
     static MOZ_MUST_USE bool requirePromise(JSContext* cx, HandleDebuggerObject object);
     static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);
 
     // JSNative properties
     static MOZ_MUST_USE bool callableGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool isBoundFunctionGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool isArrowFunctionGetter(JSContext* cx, unsigned argc, Value* vp);
+    static MOZ_MUST_USE bool isAsyncFunctionGetter(JSContext* cx, unsigned argc, Value* vp);
+    static MOZ_MUST_USE bool isGeneratorFunctionGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool protoGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool classGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool nameGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool displayNameGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool parameterNamesGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool scriptGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool environmentGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool boundTargetFunctionGetter(JSContext* cx, unsigned argc, Value* vp);