diff --git a/lib/parse.js b/lib/parse.js index 40528df1..afd87441 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -629,8 +629,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { } next_token.has_directive = function(directive) { - return S.directives[directive] !== undefined && - S.directives[directive] > 0; + return S.directives[directive] > 0; } return next_token; @@ -1033,29 +1032,32 @@ function parse($TEXT, options) { if (in_statement && !name) unexpected(); expect("("); + var argnames = []; + for (var first = true; !is("punc", ")");) { + if (first) first = false; else expect(","); + argnames.push(as_symbol(AST_SymbolFunarg)); + } + next(); + var loop = S.in_loop; + var labels = S.labels; + ++S.in_function; + S.in_directives = true; + S.input.push_directives_stack(); + S.in_loop = 0; + S.labels = []; + var body = block_(); + if (S.input.has_directive("use strict")) { + if (name) strict_verify_symbol(name); + argnames.forEach(strict_verify_symbol); + } + S.input.pop_directives_stack(); + --S.in_function; + S.in_loop = loop; + S.labels = labels; return new ctor({ name: name, - argnames: (function(first, a){ - while (!is("punc", ")")) { - if (first) first = false; else expect(","); - a.push(as_symbol(AST_SymbolFunarg)); - } - next(); - return a; - })(true, []), - body: (function(loop, labels){ - ++S.in_function; - S.in_directives = true; - S.input.push_directives_stack(); - S.in_loop = 0; - S.labels = []; - var a = block_(); - S.input.pop_directives_stack(); - --S.in_function; - S.in_loop = loop; - S.labels = labels; - return a; - })(S.in_loop, S.labels) + argnames: argnames, + body: body }); }; @@ -1157,7 +1159,10 @@ function parse($TEXT, options) { a.push(new AST_VarDef({ start : S.token, name : as_symbol(in_const ? AST_SymbolConst : AST_SymbolVar), - value : is("operator", "=") ? (next(), expression(false, no_in)) : null, + value : is("operator", "=") + ? (next(), expression(false, no_in)) + : in_const && S.input.has_directive("use strict") + ? croak("Missing initializer in const declaration") : null, end : prev() })); if (!is("punc", ",")) @@ -1384,12 +1389,20 @@ function parse($TEXT, options) { }); }; + function strict_verify_symbol(sym) { + if (sym.name == "arguments" || sym.name == "eval") + croak("Unexpected " + sym.name + " in strict mode", sym.start.line, sym.start.col, sym.start.pos); + } + function as_symbol(type, noerror) { if (!is("name")) { if (!noerror) croak("Name expected"); return null; } var sym = _make_symbol(type); + if (S.input.has_directive("use strict") && sym instanceof AST_SymbolDeclaration) { + strict_verify_symbol(sym); + } next(); return sym; }; @@ -1450,8 +1463,17 @@ function parse($TEXT, options) { function make_unary(ctor, token, expr) { var op = token.value; - if ((op == "++" || op == "--") && !is_assignable(expr)) - croak("Invalid use of " + op + " operator", token.line, token.col, token.pos); + switch (op) { + case "++": + case "--": + if (!is_assignable(expr)) + croak("Invalid use of " + op + " operator", token.line, token.col, token.pos); + break; + case "delete": + if (!(expr instanceof AST_PropAccess) && S.input.has_directive("use strict")) + croak("Calling delete on expression not allowed in strict mode", expr.start.line, expr.start.col, expr.start.pos); + break; + } return new ctor({ operator: op, expression: expr }); }; diff --git a/test/input/invalid/const.js b/test/input/invalid/const.js new file mode 100644 index 00000000..7a2bfd3d --- /dev/null +++ b/test/input/invalid/const.js @@ -0,0 +1,8 @@ +function f() { + const a; +} + +function g() { + "use strict"; + const a; +} diff --git a/test/input/invalid/delete.js b/test/input/invalid/delete.js new file mode 100644 index 00000000..ab780344 --- /dev/null +++ b/test/input/invalid/delete.js @@ -0,0 +1,8 @@ +function f(x) { + delete x; +} + +function g(x) { + "use strict"; + delete x; +} diff --git a/test/input/invalid/function_1.js b/test/input/invalid/function_1.js new file mode 100644 index 00000000..bff9c75a --- /dev/null +++ b/test/input/invalid/function_1.js @@ -0,0 +1,6 @@ +function f(arguments) { +} + +function g(arguments) { + "use strict"; +} diff --git a/test/input/invalid/function_2.js b/test/input/invalid/function_2.js new file mode 100644 index 00000000..cc496a4e --- /dev/null +++ b/test/input/invalid/function_2.js @@ -0,0 +1,6 @@ +function arguments() { +} + +function eval() { + "use strict"; +} diff --git a/test/input/invalid/function_3.js b/test/input/invalid/function_3.js new file mode 100644 index 00000000..4a20d2a6 --- /dev/null +++ b/test/input/invalid/function_3.js @@ -0,0 +1,6 @@ +!function eval() { +}(); + +!function arguments() { + "use strict"; +}(); diff --git a/test/input/invalid/try.js b/test/input/invalid/try.js new file mode 100644 index 00000000..e65a55cc --- /dev/null +++ b/test/input/invalid/try.js @@ -0,0 +1,8 @@ +function f() { + try {} catch (eval) {} +} + +function g() { + "use strict"; + try {} catch (eval) {} +} diff --git a/test/input/invalid/var.js b/test/input/invalid/var.js new file mode 100644 index 00000000..e3ccbe87 --- /dev/null +++ b/test/input/invalid/var.js @@ -0,0 +1,8 @@ +function f() { + var eval; +} + +function g() { + "use strict"; + var eval; +} diff --git a/test/mocha/cli.js b/test/mocha/cli.js index 697c09a3..523fc369 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -379,6 +379,111 @@ describe("bin/uglifyjs", function () { done(); }); }); + it("Should throw syntax error (const a)", function(done) { + var command = uglifyjscmd + ' test/input/invalid/const.js'; + + exec(command, function (err, stdout, stderr) { + assert.ok(err); + assert.strictEqual(stdout, ""); + assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ + "Parse error at test/input/invalid/const.js:7,11", + " const a;", + " ^", + "ERROR: Missing initializer in const declaration" + ].join("\n")); + done(); + }); + }); + it("Should throw syntax error (delete x)", function(done) { + var command = uglifyjscmd + ' test/input/invalid/delete.js'; + + exec(command, function (err, stdout, stderr) { + assert.ok(err); + assert.strictEqual(stdout, ""); + assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ + "Parse error at test/input/invalid/delete.js:7,11", + " delete x;", + " ^", + "ERROR: Calling delete on expression not allowed in strict mode" + ].join("\n")); + done(); + }); + }); + it("Should throw syntax error (function g(arguments))", function(done) { + var command = uglifyjscmd + ' test/input/invalid/function_1.js'; + + exec(command, function (err, stdout, stderr) { + assert.ok(err); + assert.strictEqual(stdout, ""); + assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ + "Parse error at test/input/invalid/function_1.js:4,11", + "function g(arguments) {", + " ^", + "ERROR: Unexpected arguments in strict mode" + ].join("\n")); + done(); + }); + }); + it("Should throw syntax error (function eval())", function(done) { + var command = uglifyjscmd + ' test/input/invalid/function_2.js'; + + exec(command, function (err, stdout, stderr) { + assert.ok(err); + assert.strictEqual(stdout, ""); + assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ + "Parse error at test/input/invalid/function_2.js:4,9", + "function eval() {", + " ^", + "ERROR: Unexpected eval in strict mode" + ].join("\n")); + done(); + }); + }); + it("Should throw syntax error (iife arguments())", function(done) { + var command = uglifyjscmd + ' test/input/invalid/function_3.js'; + + exec(command, function (err, stdout, stderr) { + assert.ok(err); + assert.strictEqual(stdout, ""); + assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ + "Parse error at test/input/invalid/function_3.js:4,10", + "!function arguments() {", + " ^", + "ERROR: Unexpected arguments in strict mode" + ].join("\n")); + done(); + }); + }); + it("Should throw syntax error (catch(eval))", function(done) { + var command = uglifyjscmd + ' test/input/invalid/try.js'; + + exec(command, function (err, stdout, stderr) { + assert.ok(err); + assert.strictEqual(stdout, ""); + assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ + "Parse error at test/input/invalid/try.js:7,18", + " try {} catch (eval) {}", + " ^", + "ERROR: Unexpected eval in strict mode" + ].join("\n")); + done(); + }); + }); + it("Should throw syntax error (var eval)", function(done) { + var command = uglifyjscmd + ' test/input/invalid/var.js'; + + exec(command, function (err, stdout, stderr) { + assert.ok(err); + assert.strictEqual(stdout, ""); + assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ + "Parse error at test/input/invalid/var.js:7,8", + " var eval;", + " ^", + "ERROR: Unexpected eval in strict mode" + ].join("\n")); + done(); + }); + }); it("Should handle literal string as source map input", function(done) { var command = [ uglifyjscmd,