Source code
Revision control
Copy as Markdown
Other Tools
// When a nested arrow closes over `arguments`, the enclosing ordinary function
// must own its own `arguments` binding so the arrow captures that frame's
// arguments. The frontend's UsedNameInfo::isClosedOver() only inspected the
// last recorded use, so a later `arguments` use in a newer lexical scope of the
// same function could hide the arrow's (earlier, nested-script) use. That made
// isClosedOver() report false, declareFunctionArgumentsObject() omit the local
// binding, and the arrow bind to the next farther function's arguments.
//
// Each case wraps `outer` in a `factory` with distinct arguments so a leak of
// the enclosing frame's arguments is observable: the captured arrow must return
// outer's own arguments ([1, 2, 3]), never factory's ([10, 20, 30, 40, 50]).
function assertArgs(arrow, expected) {
var args = arrow();
assertEq(args.length, expected.length);
for (var i = 0; i < expected.length; i++) {
assertEq(args[i], expected[i]);
}
}
// Control: the arrow's use is the only/last use, so isClosedOver() is correct.
(function() {
function factory() {
return function outer() {
const capture = () => arguments;
return capture;
};
}
assertArgs(factory(10, 20, 30, 40, 50)(1, 2, 3), [1, 2, 3]);
})();
// A later use in a newer nested lexical scope must not hide the arrow's use.
(function() {
function factory() {
return function outer() {
const capture = () => arguments;
{
const count = arguments.length;
return capture;
}
};
}
assertArgs(factory(10, 20, 30, 40, 50)(1, 2, 3), [1, 2, 3]);
})();
// A later use in the same scope (no new lexical scope) stays correct.
(function() {
function factory() {
return function outer() {
const capture = () => arguments;
const count = arguments.length;
return capture;
};
}
assertArgs(factory(10, 20, 30, 40, 50)(1, 2, 3), [1, 2, 3]);
})();
// A later use nested two lexical blocks deep must not hide the arrow's use.
(function() {
function factory() {
return function outer() {
const capture = () => arguments;
{
{
const count = arguments.length;
return capture;
}
}
};
}
assertArgs(factory(10, 20, 30, 40, 50)(1, 2, 3), [1, 2, 3]);
})();
// The arrow's use appearing after a same-scope use stays correct.
(function() {
function factory() {
return function outer() {
const count = arguments.length;
const capture = () => arguments;
return capture;
};
}
assertArgs(factory(10, 20, 30, 40, 50)(1, 2, 3), [1, 2, 3]);
})();
// Two arrows with a later block use between them: both must capture outer.
(function() {
function factory() {
return function outer() {
const first = () => arguments;
{
const count = arguments.length;
}
const second = () => arguments;
return [first, second];
};
}
const pair = factory(10, 20, 30, 40, 50)(1, 2, 3);
assertArgs(pair[0], [1, 2, 3]);
assertArgs(pair[1], [1, 2, 3]);
})();
// Arrow declared in one block, later use in a sibling block, must not hide it.
(function() {
function factory() {
return function outer() {
let capture;
{
capture = () => arguments;
}
{
const count = arguments.length;
}
return capture;
};
}
assertArgs(factory(10, 20, 30, 40, 50)(1, 2, 3), [1, 2, 3]);
})();
// Same elision, but with strict-mode (unmapped) arguments.
(function() {
"use strict";
function factory() {
return function outer() {
const capture = () => arguments;
{
const count = arguments.length;
return capture;
}
};
}
assertArgs(factory(10, 20, 30, 40, 50)(1, 2, 3), [1, 2, 3]);
})();