aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Lam S.L <alexlamsl@gmail.com>2018-01-04 18:45:51 +0800
committerGitHub <noreply@github.com>2018-01-04 18:45:51 +0800
commita6873a38590a9176c607bcdbff726daa93e1fec7 (patch)
treea23ff4a6df34716529d7d62b70289284c9e1f411
parent7a6d452b548c8b7783b226f646e814d6cb0cf32b (diff)
downloadtracifyjs-a6873a38590a9176c607bcdbff726daa93e1fec7.tar.gz
tracifyjs-a6873a38590a9176c607bcdbff726daa93e1fec7.zip
forbid block-scoped `AST_Defun` in strict mode (#2718)
-rw-r--r--lib/compress.js2
-rw-r--r--lib/parse.js15
-rw-r--r--test/compress/dead-code.js76
-rw-r--r--test/compress/functions.js40
-rwxr-xr-xtest/run-tests.js127
5 files changed, 76 insertions, 184 deletions
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){