From: Alex Lam S.L Date: Thu, 4 Jan 2018 10:45:51 +0000 (+0800) Subject: forbid block-scoped `AST_Defun` in strict mode (#2718) X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=a6873a38590a9176c607bcdbff726daa93e1fec7;p=UglifyJS.git forbid block-scoped `AST_Defun` in strict mode (#2718) --- diff --git a/lib/compress.js b/lib/compress.js index fe504164..481af929 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1746,7 +1746,7 @@ merge(Compressor.prototype, { target.push(node); return true; } - if (node instanceof AST_Defun && (node === stat || !compressor.has_directive("use strict"))) { + if (node instanceof AST_Defun) { target.push(node); return true; } diff --git a/lib/parse.js b/lib/parse.js index 03455348..001587bc 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -800,7 +800,7 @@ function parse($TEXT, options) { function embed_tokens(parser) { return function() { var start = S.token; - var expr = parser(); + var expr = parser.apply(null, arguments); var end = prev(); expr.start = start; expr.end = end; @@ -815,7 +815,7 @@ function parse($TEXT, options) { } }; - var statement = embed_tokens(function() { + var statement = embed_tokens(function(strict_defun) { handle_regexp(); switch (S.token.type) { case "string": @@ -901,6 +901,9 @@ function parse($TEXT, options) { return for_(); case "function": + if (!strict_defun && S.input.has_directive("use strict")) { + croak("In strict mode code, functions can only be declared at top level or immediately within another function."); + } next(); return function_(AST_Defun); @@ -1083,7 +1086,7 @@ function parse($TEXT, options) { S.input.push_directives_stack(); S.in_loop = 0; S.labels = []; - var body = block_(); + var body = block_(true); if (S.input.has_directive("use strict")) { if (name) strict_verify_symbol(name); argnames.forEach(strict_verify_symbol); @@ -1112,12 +1115,12 @@ function parse($TEXT, options) { }); }; - function block_() { + function block_(strict_defun) { expect("{"); var a = []; while (!is("punc", "}")) { if (is("eof")) unexpected(); - a.push(statement()); + a.push(statement(strict_defun)); } next(); return a; @@ -1630,7 +1633,7 @@ function parse($TEXT, options) { var body = []; S.input.push_directives_stack(); while (!is("eof")) - body.push(statement()); + body.push(statement(true)); S.input.pop_directives_stack(); var end = prev(); var toplevel = options.toplevel; diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index 32bb88e6..490cff7a 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -62,46 +62,6 @@ dead_code_2_should_warn: { node_version: "<=4" } -dead_code_2_should_warn_strict: { - options = { - dead_code: true - }; - input: { - "use strict"; - function f() { - g(); - x = 10; - throw new Error("foo"); - // completely discarding the `if` would introduce some - // bugs. UglifyJS v1 doesn't deal with this issue; in v2 - // we copy any declarations to the upper scope. - if (x) { - y(); - var x; - function g(){}; - // but nested declarations should not be kept. - (function(){ - var q; - function y(){}; - })(); - } - } - f(); - } - expect: { - "use strict"; - function f() { - g(); - x = 10; - throw new Error("foo"); - var x; - } - f(); - } - expect_stdout: true - node_version: ">=4" -} - dead_code_constant_boolean_should_warn_more: { options = { dead_code : true, @@ -137,42 +97,6 @@ dead_code_constant_boolean_should_warn_more: { node_version: "<=4" } -dead_code_constant_boolean_should_warn_more_strict: { - options = { - dead_code : true, - loops : true, - booleans : true, - conditionals : true, - evaluate : true, - side_effects : true, - }; - input: { - "use strict"; - while (!((foo && bar) || (x + "0"))) { - console.log("unreachable"); - var foo; - function bar() {} - } - for (var x = 10, y; x && (y || x) && (!typeof x); ++x) { - asdf(); - foo(); - var moo; - } - bar(); - } - expect: { - "use strict"; - var foo; - // nothing for the while - // as for the for, it should keep: - var moo; - var x = 10, y; - bar(); - } - expect_stdout: true - node_version: ">=4" -} - try_catch_finally: { options = { conditionals: true, diff --git a/test/compress/functions.js b/test/compress/functions.js index ff3baeb2..5b0c49b8 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -214,46 +214,6 @@ hoist_funs: { node_version: "<=4" } -hoist_funs_strict: { - options = { - hoist_funs: true, - } - input: { - "use strict"; - console.log(1, typeof f, typeof g); - if (console.log(2, typeof f, typeof g)) - console.log(3, typeof f, typeof g); - else { - console.log(4, typeof f, typeof g); - function f() {} - console.log(5, typeof f, typeof g); - } - function g() {} - console.log(6, typeof f, typeof g); - } - expect: { - "use strict"; - function g() {} - console.log(1, typeof f, typeof g); - if (console.log(2, typeof f, typeof g)) - console.log(3, typeof f, typeof g); - else { - console.log(4, typeof f, typeof g); - function f() {} - console.log(5, typeof f, typeof g); - } - console.log(6, typeof f, typeof g); - } - expect_stdout: [ - "1 'undefined' 'function'", - "2 'undefined' 'function'", - "4 'function' 'function'", - "5 'function' 'function'", - "6 'undefined' 'function'", - ] - node_version: ">=4" -} - issue_203: { options = { keep_fargs: false, diff --git a/test/run-tests.js b/test/run-tests.js index e95bbb83..e5f79678 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -100,6 +100,15 @@ function run_compress_tests() { quote_style: 3, keep_quoted_props: true }); + try { + U.parse(input_code); + } catch (ex) { + log("!!! Cannot parse input\n---INPUT---\n{input}\n--PARSE ERROR--\n{error}\n\n", { + input: input_formatted, + error: ex, + }); + return false; + } var options = U.defaults(test.options, { warnings: false }); @@ -139,78 +148,74 @@ function run_compress_tests() { output: output, expected: expect }); - failures++; - failed_files[file] = 1; + return false; } - else { - // expect == output - try { - var reparsed_ast = U.parse(output); - } catch (ex) { - log("!!! Test matched expected result but cannot parse output\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n--REPARSE ERROR--\n{error}\n\n", { + // expect == output + try { + U.parse(output); + } catch (ex) { + log("!!! Test matched expected result but cannot parse output\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n--REPARSE ERROR--\n{error}\n\n", { + input: input_formatted, + output: output, + error: ex, + }); + return false; + } + if (test.expect_warnings) { + U.AST_Node.warn_function = original_warn_function; + var expected_warnings = make_code(test.expect_warnings, { + beautify: false, + quote_style: 2, // force double quote to match JSON + }); + warnings_emitted = warnings_emitted.map(function(input) { + return input.split(process.cwd() + path.sep).join("").split(path.sep).join("/"); + }); + var actual_warnings = JSON.stringify(warnings_emitted); + if (expected_warnings != actual_warnings) { + log("!!! failed\n---INPUT---\n{input}\n---EXPECTED WARNINGS---\n{expected_warnings}\n---ACTUAL WARNINGS---\n{actual_warnings}\n\n", { input: input_formatted, - output: output, - error: ex.toString(), + expected_warnings: expected_warnings, + actual_warnings: actual_warnings, }); - failures++; - failed_files[file] = 1; + return false; } - if (test.expect_warnings) { - U.AST_Node.warn_function = original_warn_function; - var expected_warnings = make_code(test.expect_warnings, { - beautify: false, - quote_style: 2, // force double quote to match JSON - }); - warnings_emitted = warnings_emitted.map(function(input) { - return input.split(process.cwd() + path.sep).join("").split(path.sep).join("/"); + } + if (test.expect_stdout + && (!test.node_version || semver.satisfies(process.version, test.node_version))) { + var stdout = sandbox.run_code(input_code); + if (test.expect_stdout === true) { + test.expect_stdout = stdout; + } + if (!sandbox.same_stdout(test.expect_stdout, stdout)) { + log("!!! Invalid input or expected stdout\n---INPUT---\n{input}\n---EXPECTED {expected_type}---\n{expected}\n---ACTUAL {actual_type}---\n{actual}\n\n", { + input: input_formatted, + expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR", + expected: test.expect_stdout, + actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR", + actual: stdout, }); - var actual_warnings = JSON.stringify(warnings_emitted); - if (expected_warnings != actual_warnings) { - log("!!! failed\n---INPUT---\n{input}\n---EXPECTED WARNINGS---\n{expected_warnings}\n---ACTUAL WARNINGS---\n{actual_warnings}\n\n", { - input: input_formatted, - expected_warnings: expected_warnings, - actual_warnings: actual_warnings, - }); - failures++; - failed_files[file] = 1; - } + return false; } - if (test.expect_stdout - && (!test.node_version || semver.satisfies(process.version, test.node_version))) { - var stdout = sandbox.run_code(input_code); - if (test.expect_stdout === true) { - test.expect_stdout = stdout; - } - if (!sandbox.same_stdout(test.expect_stdout, stdout)) { - log("!!! Invalid input or expected stdout\n---INPUT---\n{input}\n---EXPECTED {expected_type}---\n{expected}\n---ACTUAL {actual_type}---\n{actual}\n\n", { - input: input_formatted, - expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR", - expected: test.expect_stdout, - actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR", - actual: stdout, - }); - failures++; - failed_files[file] = 1; - } else { - stdout = sandbox.run_code(output); - if (!sandbox.same_stdout(test.expect_stdout, stdout)) { - log("!!! failed\n---INPUT---\n{input}\n---EXPECTED {expected_type}---\n{expected}\n---ACTUAL {actual_type}---\n{actual}\n\n", { - input: input_formatted, - expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR", - expected: test.expect_stdout, - actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR", - actual: stdout, - }); - failures++; - failed_files[file] = 1; - } - } + stdout = sandbox.run_code(output); + if (!sandbox.same_stdout(test.expect_stdout, stdout)) { + log("!!! failed\n---INPUT---\n{input}\n---EXPECTED {expected_type}---\n{expected}\n---ACTUAL {actual_type}---\n{actual}\n\n", { + input: input_formatted, + expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR", + expected: test.expect_stdout, + actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR", + actual: stdout, + }); + return false; } } + return true; } var tests = parse_test(path.resolve(dir, file)); for (var i in tests) if (tests.hasOwnProperty(i)) { - test_case(tests[i]); + if (!test_case(tests[i])) { + failures++; + failed_files[file] = 1; + } } } files.forEach(function(file){