Bug 1382258 - Handle async functions properly in Debugger API. r=tromey

An async function or generator is represented by a native function wrapped
around a scripted function. In order to avoid treating async functions as if
they were opaque native functions, Debugger must explicitly look through these
wrappers to find the scripted function inside.
This commit is contained in:
Jim Blandy 2017-07-25 10:48:28 -07:00
Родитель 1e3c594756
Коммит 7944af2cd7
7 изменённых файлов: 90 добавлений и 20 удалений

Просмотреть файл

@ -15,3 +15,7 @@ g.eval("var f = function() { debugger; }; f()");
assertEq(name, "f");
g.eval("var a = {}; a.f = function() { debugger; }; a.f()");
assertEq(name, "a.f");
g.eval("(async function grondo() { debugger; })();");
assertEq(name, "grondo");
g.eval("(async function* estux() { debugger; })().next();");
assertEq(name, "estux");

Просмотреть файл

@ -9,12 +9,21 @@ g.h = function () {
assertEq(fn.environment instanceof Debugger.Environment, true);
var closure = frame.eval("f").return;
assertEq(closure.environment instanceof Debugger.Environment, true);
var async_fun = frame.eval("m").return;
assertEq(async_fun.environment instanceof Debugger.Environment, true);
var async_iter = frame.eval("n").return;
assertEq(async_iter.environment instanceof Debugger.Environment, true);
hits++;
};
g.eval("function j(a) {\n" +
" var f = function () { return a; };\n" +
" h();\n" +
" return f;\n" +
"}\n" +
"j(0);\n");
g.eval(`
function j(a) {
var f = function () { return a; };
function* g() { }
async function m() { }
async function* n() { }
h();
return f;
}
j(0);
`);
assertEq(hits, 1);

Просмотреть файл

@ -20,3 +20,5 @@ checkIsArrow(false, 'Math.atan2');
checkIsArrow(false, 'Function.prototype');
checkIsArrow(false, 'Function("")');
checkIsArrow(false, 'new Function("")');
checkIsArrow(false, '(async function f () {})');
checkIsArrow(true, '(async () => { })');

Просмотреть файл

@ -11,3 +11,7 @@ g.eval("(function () { debugger; })();");
assertEq(name, undefined);
g.eval("Function('debugger;')();");
assertEq(name, "anonymous");
g.eval("(async function grondo() { debugger; })();");
assertEq(name, "grondo");
g.eval("(async function* estux() { debugger; })().next();");
assertEq(name, "estux");

Просмотреть файл

@ -9,24 +9,28 @@ dbg.onDebuggerStatement = function (frame) {
assertEq(arraysEqual(arr[1].parameterNames, ["x"]), true);
assertEq(arraysEqual(arr[2].parameterNames,
["a","b","c","d","e","f","g","h","i","j","k","l","m",
"n","o","p","q","r","s","t","u","v","w","x","y","z"]),
"n","o","p","q","r","s","t","u","v","w","x","y","z"]),
true);
assertEq(arraysEqual(arr[3].parameterNames, ["a", (void 0), (void 0)]), true);
assertEq(arr[4].parameterNames, (void 0));
assertEq(arraysEqual(arr[5].parameterNames, [(void 0), (void 0)]), true);
assertEq(arr.length, 6);
assertEq(arraysEqual(arr[6].parameterNames, ["a", "b", "c"]), true);
assertEq(arraysEqual(arr[7].parameterNames, ["d", "e", "f"]), true);
assertEq(arr.length, 8);
hits++;
};
g.eval("("
+ function () {
+ function () {
(function () { debugger; }
(function () {},
function (x) {},
function (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z) {},
function (a, [b, c], {d, e:f}) { },
{a:1},
Math.atan2
Math.atan2,
async function (a, b, c) {},
async function* (d, e, f) {}
));
}
+")()");

Просмотреть файл

@ -4,10 +4,22 @@ var hits = 0;
dbg.onDebuggerStatement = function (frame) {
var arr = frame.arguments;
assertEq(arr[0].script instanceof Debugger.Script, true);
assertEq(arr[1].script, undefined);
assertEq(arr[2].script, undefined);
assertEq(arr[1].script instanceof Debugger.Script, true);
assertEq(arr[2].script instanceof Debugger.Script, true);
assertEq(arr[3].script instanceof Debugger.Script, true);
assertEq(arr[4].script, undefined);
assertEq(arr[5].script, undefined);
assertEq(arr.length, 6);
hits++;
};
g.eval("(function () { debugger; })(function g(){}, {}, Math.atan2);");
g.eval(`
function f() { debugger; }
f(function g(){},
function* h() {},
async function j() {},
async function* k() {},
{},
Math.atan2);
`);
assertEq(hits, 1);

Просмотреть файл

@ -32,6 +32,8 @@
#include "js/Vector.h"
#include "proxy/ScriptedProxyHandler.h"
#include "vm/ArgumentsObject.h"
#include "vm/AsyncFunction.h"
#include "vm/AsyncIteration.h"
#include "vm/DebuggerMemory.h"
#include "vm/GeckoProfiler.h"
#include "vm/GeneratorObject.h"
@ -227,6 +229,34 @@ static const Class DebuggerSource_class = {
/*** Utils ***************************************************************************************/
/*
* If fun is an interpreted function, remove any async function/generator
* wrapper and return the underlying scripted function. Otherwise, return fun
* unchanged.
*
* Async functions are implemented as native functions wrapped around a scripted
* function. JSScripts hold ordinary inner JSFunctions in their object arrays,
* and when we need to actually create a JS-visible function object, we build an
* ordinary JS closure and apply the async wrapper to it. Async generators are
* similar.
*
* This means that JSFunction::isInterpreted returns false for such functions,
* even though their actual code is indeed JavaScript. Debugger should treat
* async functions and generators like any other scripted function, so we must
* carefully check for them whenever we want inspect a function.
*/
static JSFunction*
RemoveAsyncWrapper(JSFunction *fun)
{
if (js::IsWrappedAsyncFunction(fun))
fun = js::GetUnwrappedAsyncFunction(fun);
else if (js::IsWrappedAsyncGenerator(fun))
fun = js::GetUnwrappedAsyncGenerator(fun);
return fun;
}
static inline bool
EnsureFunctionHasScript(JSContext* cx, HandleFunction fun)
{
@ -9247,7 +9277,7 @@ DebuggerObject::scriptGetter(JSContext* cx, unsigned argc, Value* vp)
return true;
}
RootedFunction fun(cx, &obj->as<JSFunction>());
RootedFunction fun(cx, RemoveAsyncWrapper(&obj->as<JSFunction>()));
if (!fun->isInterpreted()) {
args.rval().setUndefined();
return true;
@ -9277,21 +9307,26 @@ DebuggerObject::environmentGetter(JSContext* cx, unsigned argc, Value* vp)
THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get environment", args, dbg, obj);
/* Don't bother switching compartments just to check obj's type and get its env. */
if (!obj->is<JSFunction>() || !obj->as<JSFunction>().isInterpreted()) {
if (!obj->is<JSFunction>()) {
args.rval().setUndefined();
return true;
}
RootedFunction fun(cx, RemoveAsyncWrapper(&obj->as<JSFunction>()));
if (!fun->isInterpreted()) {
args.rval().setUndefined();
return true;
}
/* Only hand out environments of debuggee functions. */
if (!dbg->observesGlobal(&obj->global())) {
if (!dbg->observesGlobal(&fun->global())) {
args.rval().setNull();
return true;
}
Rooted<Env*> env(cx);
{
AutoCompartment ac(cx, obj);
RootedFunction fun(cx, &obj->as<JSFunction>());
AutoCompartment ac(cx, fun);
env = GetDebugEnvironmentForFunction(cx, fun);
if (!env)
return false;
@ -10178,7 +10213,7 @@ DebuggerObject::isArrowFunction() const
{
MOZ_ASSERT(isDebuggeeFunction());
return referent()->as<JSFunction>().isArrow();
return RemoveAsyncWrapper(&referent()->as<JSFunction>())->isArrow();
}
bool
@ -10286,7 +10321,7 @@ DebuggerObject::getParameterNames(JSContext* cx, HandleDebuggerObject object,
{
MOZ_ASSERT(object->isDebuggeeFunction());
RootedFunction referent(cx, &object->referent()->as<JSFunction>());
RootedFunction referent(cx, RemoveAsyncWrapper(&object->referent()->as<JSFunction>()));
if (!result.growBy(referent->nargs()))
return false;