Fix arrow function lexical arguments binding, allow rest + arguments

Issue #1547
This commit is contained in:
janekptacijarabaci
2017-12-18 13:22:21 +01:00
committed by Roy Tam
parent fd05ca22e4
commit 7aaedab9c0
18 changed files with 137 additions and 137 deletions
-7
View File
@@ -65,13 +65,6 @@ SetSourceMap(ExclusiveContext* cx, TokenStream& tokenStream, ScriptSource* ss)
static bool
CheckArgumentsWithinEval(JSContext* cx, Parser<FullParseHandler>& parser, HandleFunction fun)
{
if (fun->hasRest()) {
// It's an error to use |arguments| in a function that has a rest
// parameter.
parser.report(ParseError, false, nullptr, JSMSG_ARGUMENTS_AND_REST);
return false;
}
RootedScript script(cx, fun->getOrCreateScript(cx));
if (!script)
return false;
-3
View File
@@ -6917,8 +6917,6 @@ frontend::EmitTree(ExclusiveContext* cx, BytecodeEmitter* bce, ParseNode* pn)
ParseNode* rest = nullptr;
bool restIsDefn = false;
if (fun->hasRest()) {
MOZ_ASSERT(!bce->sc->asFunctionBox()->argumentsHasLocalBinding());
// Defaults with a rest parameter need special handling. The
// rest parameter needs to be undefined while defaults are being
// processed. To do this, we create the rest argument and let it
@@ -6964,7 +6962,6 @@ frontend::EmitTree(ExclusiveContext* cx, BytecodeEmitter* bce, ParseNode* pn)
return false;
if (pn2->pn_next == pnlast && fun->hasRest() && !hasDefaults) {
// Fill rest parameter. We handled the case with defaults above.
MOZ_ASSERT(!bce->sc->asFunctionBox()->argumentsHasLocalBinding());
bce->switchToProlog();
if (Emit1(cx, bce, JSOP_REST) < 0)
return false;
+12 -31
View File
@@ -909,10 +909,6 @@ Parser<FullParseHandler>::checkFunctionArguments()
}
}
/*
* Report error if both rest parameters and 'arguments' are used. Do this
* check before adding artificial 'arguments' below.
*/
Definition* maybeArgDef = pc->decls().lookupFirst(arguments);
bool argumentsHasBinding = !!maybeArgDef;
// ES6 9.2.13.17 says that a lexical binding of 'arguments' shadows the
@@ -920,18 +916,12 @@ Parser<FullParseHandler>::checkFunctionArguments()
bool argumentsHasLocalBinding = maybeArgDef && (maybeArgDef->kind() != Definition::ARG &&
maybeArgDef->kind() != Definition::LET &&
maybeArgDef->kind() != Definition::CONST);
bool hasRest = pc->sc->asFunctionBox()->function()->hasRest();
if (hasRest && argumentsHasLocalBinding) {
report(ParseError, false, nullptr, JSMSG_ARGUMENTS_AND_REST);
return false;
}
/*
* Even if 'arguments' isn't explicitly mentioned, dynamic name lookup
* forces an 'arguments' binding. The exception is that functions with rest
* parameters are free from 'arguments'.
* forces an 'arguments' binding.
*/
if (!argumentsHasBinding && pc->sc->bindingsAccessedDynamically() && !hasRest) {
if (!argumentsHasBinding && pc->sc->bindingsAccessedDynamically()) {
ParseNode* pn = newName(arguments);
if (!pn)
return false;
@@ -989,21 +979,8 @@ template <>
bool
Parser<SyntaxParseHandler>::checkFunctionArguments()
{
bool hasRest = pc->sc->asFunctionBox()->function()->hasRest();
if (pc->lexdeps->lookup(context->names().arguments)) {
if (pc->lexdeps->lookup(context->names().arguments))
pc->sc->asFunctionBox()->usesArguments = true;
if (hasRest) {
report(ParseError, false, null(), JSMSG_ARGUMENTS_AND_REST);
return false;
}
} else if (hasRest) {
DefinitionNode maybeArgDef = pc->decls().lookupFirst(context->names().arguments);
if (maybeArgDef && handler.getDefinitionKind(maybeArgDef) != Definition::ARG) {
report(ParseError, false, null(), JSMSG_ARGUMENTS_AND_REST);
return false;
}
}
return true;
}
@@ -1088,9 +1065,12 @@ Parser<ParseHandler>::functionBody(FunctionSyntaxKind kind, FunctionBodyType typ
return null();
}
/* Define the 'arguments' binding if necessary. */
if (!checkFunctionArguments())
return null();
if (kind != Arrow) {
// Define the 'arguments' binding if necessary. Arrow functions
// don't have 'arguments'.
if (!checkFunctionArguments())
return null();
}
return pn;
}
@@ -1982,8 +1962,9 @@ Parser<ParseHandler>::addFreeVariablesFromLazyFunction(JSFunction* fun,
for (size_t i = 0; i < lazy->numFreeVariables(); i++) {
JSAtom* atom = freeVariables[i].atom();
// 'arguments' will be implicitly bound within the inner function.
if (atom == context->names().arguments)
// 'arguments' will be implicitly bound within the inner function,
// except if the inner function is an arrow function.
if (atom == context->names().arguments && !fun->isArrow())
continue;
DefinitionNode dn = pc->decls().lookupFirst(atom);
+1 -6
View File
@@ -16,9 +16,4 @@ function bar() {
bar();
(function(){assertEq(typeof eval("var arguments; arguments"), "object")})();
try {
(function(... rest){assertEq(typeof eval("var arguments; arguments"), "object")})();
assertEq(false, true);
} catch (e) {
assertEq(/SyntaxError/.test(e), true);
}
(function(... rest){assertEq(typeof eval("var arguments; arguments"), "object")})();
@@ -9,9 +9,9 @@ var g = newGlobal();
g.eval("function f(...rest) { debugger; }");
var dbg = Debugger(g);
dbg.onDebuggerStatement = function (frame) {
var result = frame.eval("arguments");
assertEq("throw" in result, true);
var result2 = frame.evalWithBindings("exc instanceof SyntaxError", {exc: result.throw});
assertEq(result2.return, true);
frame.eval("args = arguments");
};
g.f();
g.f(9, 8, 7);
assertEq(g.args.length, 3);
assertEq(g.args[2], 7);
@@ -1,30 +0,0 @@
load(libdir + "asserts.js");
var ieval = eval;
// Now for a tour of the various ways you can access arguments.
assertThrowsInstanceOf(function () {
ieval("function x(...rest) { arguments; }");
}, SyntaxError)
assertThrowsInstanceOf(function () {
Function("...rest", "arguments;");
}, SyntaxError);
assertThrowsInstanceOf(function (...rest) {
eval("arguments;");
}, SyntaxError);
assertThrowsInstanceOf(function (...rest) {
eval("arguments = 42;");
}, SyntaxError);
function g(...rest) {
assertThrowsInstanceOf(h, Error);
}
function h() {
g.arguments;
}
g();
// eval() is evil, but you can still use it with rest parameters!
function still_use_eval(...rest) {
eval("x = 4");
}
still_use_eval();
@@ -0,0 +1,45 @@
// 'arguments' is allowed with rest parameters.
// FIXME: We should create an unmapped arguments object in this case,
// see bug 1175394. This test is not correct until then.
var args;
function restWithArgs(a, b, ...rest) {
return arguments;
}
args = restWithArgs(1, 3, 6, 9);
assertEq(args.length, 4);
assertEq(JSON.stringify(args), '{"0":1,"1":3,"2":[6,9],"3":9}',
"Did you just fix bug 1175394?");
args = restWithArgs();
assertEq(args.length, 0);
args = restWithArgs(4, 5);
assertEq(args.length, 2);
assertEq(JSON.stringify(args), '{"0":4,"1":5}');
function restWithArgsEval(a, b, ...rest) {
return eval("arguments");
}
args = restWithArgsEval(1, 3, 6, 9);
assertEq(args.length, 4);
assertEq(JSON.stringify(args), '{"0":1,"1":3,"2":[6,9],"3":9}',
"Did you just fix bug 1175394?");
function g(...rest) {
h();
}
function h() {
g.arguments;
}
g();
// eval() is evil, but you can still use it with rest parameters!
function still_use_eval(...rest) {
eval("x = 4");
}
still_use_eval();
@@ -1,13 +1,5 @@
// arrow functions have an 'arguments' binding, like any other function
// no 'arguments' binding in arrow functions
var arguments = [];
var f = () => arguments;
var args = f();
assertEq(args.length, 0);
assertEq(Object.getPrototypeOf(args), Object.prototype);
args = f(true, false);
assertEq(args.length, 2);
assertEq(args[0], true);
assertEq(args[1], false);
assertEq(f(), arguments);
@@ -1,14 +1,9 @@
// 'arguments' in arrow functions nested in other functions
// 'arguments' is lexically scoped in arrow functions
var g;
var args, g;
function f() {
g = () => arguments;
args = arguments;
}
f();
var args = g();
assertEq(args.length, 0);
args = g(1, 2, 3);
assertEq(args.length, 3);
assertEq(args[0], 1);
assertEq(args[2], 3);
assertEq(g(), args);
@@ -1,13 +1,17 @@
// the 'arguments' binding in an arrow function is visible in direct eval code
// 'arguments' in eval
function f() {
return s => eval(s);
var g = s => eval(s);
assertEq(g("arguments"), arguments);
}
var g = f();
var args = g("arguments");
assertEq(typeof args, "object");
assertEq(args !== g("arguments"), true);
assertEq(args.length, 1);
assertEq(args[0], "arguments");
f();
f(0, 1, 2);
function h() {
return s => eval(s);
}
var result = h(1, 2, 3, 4)("arguments");
assertEq(result.length, 4);
assertEq(result[3], 4);
@@ -1,14 +1,22 @@
// 'arguments' is banned in a function with a rest param,
// even nested in an arrow-function parameter default value
load(libdir + "asserts.js");
var mistakes = [
"(...rest) => arguments",
"(...rest) => (x=arguments) => 0",
"function f(...rest) { return (x=arguments) => 0; }",
"function f(...rest) { return (x=(y=arguments) => 1) => 0; }",
];
// 'arguments' is allowed in a non-arrow-function with a rest param.
assertEq((function(...rest) { return (x => arguments)(1, 2)})().length, 0);
for (var s of mistakes)
assertThrowsInstanceOf(function () { eval(s); }, SyntaxError);
function restAndArgs(...rest) {
return () => eval("arguments");
}
var args = restAndArgs(1, 2, 3)();
assertEq(args.length, 3);
assertDeepEq(args[0], [1, 2, 3], "This is bogus, see bug 1175394");
assertEq(args[1], 2);
assertEq(args[2], 3);
(function() {
return ((...rest) => {
assertDeepEq(rest, [1, 2, 3]);
assertEq(arguments.length, 2);
assertEq(eval("arguments").length, 2);
})(1, 2, 3);
})(4, 5);
@@ -1,4 +1,28 @@
// deoptimize `arguments` in the arrow's closest enclosing non-arrow-function
// non-arrow-function -> arrow function
a = 0;
(function() {
a = (b => eval("arguments"))();
a = (() => eval("arguments"))();
})(1, 2, 3, 4);
assertEq(a.length, 0);
assertEq(a.length, 4);
// non-arrow-function -> arrow function -> arrow function
a = 0;
(function() {
(() => {
a = (() => eval("arguments"))();
})();
})(1, 2, 3, 4);
assertEq(a.length, 4);
// non-arrow-function -> arrow function -> non-arrow-function -> arrow function
a = 0;
(function() {
(() => {
(function () {
a = (() => eval("arguments"))();
})(1, 2, 3, 4);
})();
})();
assertEq(a.length, 4);
@@ -46,8 +46,6 @@ function checkLength(f, makeFn) {
checkLength(function(x) arguments.length, makeCall);
checkLength(function(x) arguments.length, makeFunCall);
checkLength((x) => arguments.length, makeCall);
checkLength((x) => arguments.length, makeFunCall);
function lengthClass(x) {
this.length = arguments.length;
}
+9
View File
@@ -9129,6 +9129,15 @@ IonBuilder::jsop_arguments()
bool
IonBuilder::jsop_rest()
{
if (info().analysisMode() == Analysis_ArgumentsUsage) {
// There's no BaselineScript with the template object. Just push a
// dummy value, it does not affect the arguments analysis.
MUnknownValue* unknown = MUnknownValue::New(alloc());
current->add(unknown);
current->push(unknown);
return true;
}
ArrayObject* templateObject = &inspector->getTemplateObject(pc)->as<ArrayObject>();
if (inliningDepth_ == 0) {
-2
View File
@@ -131,7 +131,6 @@ MSG_DEF(JSMSG_BAD_APPLY_ARGS, 1, JSEXN_TYPEERR, "second argument to Fun
MSG_DEF(JSMSG_BAD_FORMAL, 0, JSEXN_SYNTAXERR, "malformed formal parameter")
MSG_DEF(JSMSG_CALLER_IS_STRICT, 0, JSEXN_TYPEERR, "access to strict mode caller function is censored")
MSG_DEF(JSMSG_DEPRECATED_USAGE, 1, JSEXN_REFERENCEERR, "deprecated {0} usage")
MSG_DEF(JSMSG_FUNCTION_ARGUMENTS_AND_REST, 0, JSEXN_TYPEERR, "the 'arguments' property of a function with a rest parameter may not be used")
MSG_DEF(JSMSG_NOT_SCRIPTED_FUNCTION, 1, JSEXN_TYPEERR, "{0} is not a scripted function")
MSG_DEF(JSMSG_NO_REST_NAME, 0, JSEXN_SYNTAXERR, "no parameter name after ...")
MSG_DEF(JSMSG_PARAMETER_AFTER_REST, 0, JSEXN_SYNTAXERR, "parameter after rest parameter")
@@ -173,7 +172,6 @@ MSG_DEF(JSMSG_UNKNOWN_FORMAT, 1, JSEXN_INTERNALERR, "unknown bytecode f
// Frontend
MSG_DEF(JSMSG_ACCESSOR_WRONG_ARGS, 3, JSEXN_SYNTAXERR, "{0} functions must have {1} argument{2}")
MSG_DEF(JSMSG_ARGUMENTS_AND_REST, 0, JSEXN_SYNTAXERR, "'arguments' object may not be used in conjunction with a rest parameter")
MSG_DEF(JSMSG_ARRAY_COMP_LEFTSIDE, 0, JSEXN_SYNTAXERR, "invalid array comprehension left-hand side")
MSG_DEF(JSMSG_ARRAY_INIT_TOO_BIG, 0, JSEXN_INTERNALERR, "array initialiser too large")
MSG_DEF(JSMSG_AS_AFTER_RESERVED_WORD, 1, JSEXN_SYNTAXERR, "missing keyword 'as' after reserved word '{0}'")
-8
View File
@@ -128,14 +128,6 @@ ArgumentsRestrictions(JSContext* cx, HandleFunction fun)
return false;
}
// Functions with rest arguments don't include a local |arguments| binding.
// Similarly, "arguments" shouldn't work on them.
if (fun->hasRest()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
JSMSG_FUNCTION_ARGUMENTS_AND_REST);
return false;
}
// Otherwise emit a strict warning about |f.arguments| to discourage use of
// this non-standard, performance-harmful feature.
if (!JS_ReportErrorFlagsAndNumber(cx, JSREPORT_WARNING | JSREPORT_STRICT, js_GetErrorMessage,
-1
View File
@@ -2893,7 +2893,6 @@ CASE(JSOP_TABLESWITCH)
}
CASE(JSOP_ARGUMENTS)
MOZ_ASSERT(!REGS.fp()->fun()->hasRest());
if (!script->ensureHasAnalyzedArgsUsage(cx))
goto error;
if (script->needsArgsObj()) {
+2 -2
View File
@@ -29,11 +29,11 @@ namespace js {
*
* https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
*/
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 250;
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 251;
static const uint32_t XDR_BYTECODE_VERSION =
uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
static_assert(JSErr_Limit == 367,
static_assert(JSErr_Limit == 365,
"GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or "
"removed MSG_DEFs from js.msg, you should increment "
"XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's "