Skip to content
This repository has been archived by the owner on Aug 4, 2022. It is now read-only.

Commit

Permalink
Bug 889158 - Fix arrow function lexical arguments binding, allow rest…
Browse files Browse the repository at this point in the history
… + arguments. r=jorendorff
  • Loading branch information
jandem committed Aug 25, 2015
1 parent 7ea8099 commit 4b0ef0b
Show file tree
Hide file tree
Showing 19 changed files with 136 additions and 141 deletions.
7 changes: 0 additions & 7 deletions js/src/frontend/BytecodeCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -422,13 +422,6 @@ BytecodeCompiler::maybeSetSourceMapFromOptions()
bool
BytecodeCompiler::checkArgumentsWithinEval(JSContext* cx, 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;
Expand Down
3 changes: 0 additions & 3 deletions js/src/frontend/BytecodeEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7583,8 +7583,6 @@ BytecodeEmitter::emitTree(ParseNode* pn)
ParseNode* rest = nullptr;
bool restIsDefn = false;
if (fun->hasRest() && hasDefaults) {
MOZ_ASSERT(!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
Expand Down Expand Up @@ -7629,7 +7627,6 @@ BytecodeEmitter::emitTree(ParseNode* pn)
return false;
if (pn2->pn_next == pnlast && fun->hasRest() && !hasDefaults) {
// Fill rest parameter. We handled the case with defaults above.
MOZ_ASSERT(!sc->asFunctionBox()->argumentsHasLocalBinding());
switchToPrologue();
if (!emit1(JSOP_REST))
return false;
Expand Down
43 changes: 12 additions & 31 deletions js/src/frontend/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1002,29 +1002,19 @@ 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
// arguments object.
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;
Expand Down Expand Up @@ -1082,21 +1072,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;
}
Expand Down Expand Up @@ -1182,9 +1159,12 @@ Parser<ParseHandler>::functionBody(InHandling inHandling, YieldHandling yieldHan
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;
}
Expand Down Expand Up @@ -2205,8 +2185,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);
Expand Down
7 changes: 1 addition & 6 deletions js/src/jit-test/tests/arguments/bug844048.js
Original file line number Diff line number Diff line change
Expand Up @@ -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")})();
10 changes: 5 additions & 5 deletions js/src/jit-test/tests/arguments/rest-debugger.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
30 changes: 0 additions & 30 deletions js/src/jit-test/tests/arguments/rest-disallow-arguments.js

This file was deleted.

45 changes: 45 additions & 0 deletions js/src/jit-test/tests/arguments/rest-with-arguments.js
Original file line number Diff line number Diff line change
@@ -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();
12 changes: 2 additions & 10 deletions js/src/jit-test/tests/arrow-functions/arguments-1.js
Original file line number Diff line number Diff line change
@@ -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);
13 changes: 4 additions & 9 deletions js/src/jit-test/tests/arrow-functions/arguments-2.js
Original file line number Diff line number Diff line change
@@ -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);
19 changes: 11 additions & 8 deletions js/src/jit-test/tests/arrow-functions/arguments-3.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
// 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);
30 changes: 19 additions & 11 deletions js/src/jit-test/tests/arrow-functions/arguments-4.js
Original file line number Diff line number Diff line change
@@ -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);

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);

for (var s of mistakes)
assertThrowsInstanceOf(function () { eval(s); }, SyntaxError);
(function() {
return ((...rest) => {
assertDeepEq(rest, [1, 2, 3]);
assertEq(arguments.length, 2);
assertEq(eval("arguments").length, 2);
})(1, 2, 3);
})(4, 5);
28 changes: 26 additions & 2 deletions js/src/jit-test/tests/arrow-functions/bug-885067-2.js
Original file line number Diff line number Diff line change
@@ -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);
4 changes: 0 additions & 4 deletions js/src/jit-test/tests/arrow-functions/bug889158.js

This file was deleted.

2 changes: 0 additions & 2 deletions js/src/jit-test/tests/basic/spread-call-length.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
9 changes: 9 additions & 0 deletions js/src/jit/IonBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9735,6 +9735,15 @@ IonBuilder::jsop_newtarget()
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) {
Expand Down
2 changes: 0 additions & 2 deletions js/src/js.msg
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,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")
Expand Down Expand Up @@ -181,7 +180,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_IMPORT_STAR, 0, JSEXN_SYNTAXERR, "missing keyword 'as' after import *")
Expand Down
Loading

0 comments on commit 4b0ef0b

Please sign in to comment.