forbid block-scoped AST_Defun in strict mode (#2718)

This commit is contained in:
Alex Lam S.L
2018-01-04 18:45:51 +08:00
committed by GitHub
parent 7a6d452b54
commit a6873a3859
5 changed files with 79 additions and 187 deletions

View File

@@ -1746,7 +1746,7 @@ merge(Compressor.prototype, {
target.push(node); target.push(node);
return true; return true;
} }
if (node instanceof AST_Defun && (node === stat || !compressor.has_directive("use strict"))) { if (node instanceof AST_Defun) {
target.push(node); target.push(node);
return true; return true;
} }

View File

@@ -800,7 +800,7 @@ function parse($TEXT, options) {
function embed_tokens(parser) { function embed_tokens(parser) {
return function() { return function() {
var start = S.token; var start = S.token;
var expr = parser(); var expr = parser.apply(null, arguments);
var end = prev(); var end = prev();
expr.start = start; expr.start = start;
expr.end = end; 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(); handle_regexp();
switch (S.token.type) { switch (S.token.type) {
case "string": case "string":
@@ -901,6 +901,9 @@ function parse($TEXT, options) {
return for_(); return for_();
case "function": 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(); next();
return function_(AST_Defun); return function_(AST_Defun);
@@ -1083,7 +1086,7 @@ function parse($TEXT, options) {
S.input.push_directives_stack(); S.input.push_directives_stack();
S.in_loop = 0; S.in_loop = 0;
S.labels = []; S.labels = [];
var body = block_(); var body = block_(true);
if (S.input.has_directive("use strict")) { if (S.input.has_directive("use strict")) {
if (name) strict_verify_symbol(name); if (name) strict_verify_symbol(name);
argnames.forEach(strict_verify_symbol); argnames.forEach(strict_verify_symbol);
@@ -1112,12 +1115,12 @@ function parse($TEXT, options) {
}); });
}; };
function block_() { function block_(strict_defun) {
expect("{"); expect("{");
var a = []; var a = [];
while (!is("punc", "}")) { while (!is("punc", "}")) {
if (is("eof")) unexpected(); if (is("eof")) unexpected();
a.push(statement()); a.push(statement(strict_defun));
} }
next(); next();
return a; return a;
@@ -1630,7 +1633,7 @@ function parse($TEXT, options) {
var body = []; var body = [];
S.input.push_directives_stack(); S.input.push_directives_stack();
while (!is("eof")) while (!is("eof"))
body.push(statement()); body.push(statement(true));
S.input.pop_directives_stack(); S.input.pop_directives_stack();
var end = prev(); var end = prev();
var toplevel = options.toplevel; var toplevel = options.toplevel;

View File

@@ -62,46 +62,6 @@ dead_code_2_should_warn: {
node_version: "<=4" 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: { dead_code_constant_boolean_should_warn_more: {
options = { options = {
dead_code : true, dead_code : true,
@@ -137,42 +97,6 @@ dead_code_constant_boolean_should_warn_more: {
node_version: "<=4" 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: { try_catch_finally: {
options = { options = {
conditionals: true, conditionals: true,

View File

@@ -214,46 +214,6 @@ hoist_funs: {
node_version: "<=4" 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: { issue_203: {
options = { options = {
keep_fargs: false, keep_fargs: false,

View File

@@ -100,6 +100,15 @@ function run_compress_tests() {
quote_style: 3, quote_style: 3,
keep_quoted_props: true 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, { var options = U.defaults(test.options, {
warnings: false warnings: false
}); });
@@ -139,21 +148,18 @@ function run_compress_tests() {
output: output, output: output,
expected: expect expected: expect
}); });
failures++; return false;
failed_files[file] = 1;
} }
else {
// expect == output // expect == output
try { try {
var reparsed_ast = U.parse(output); U.parse(output);
} catch (ex) { } 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", { 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, input: input_formatted,
output: output, output: output,
error: ex.toString(), error: ex,
}); });
failures++; return false;
failed_files[file] = 1;
} }
if (test.expect_warnings) { if (test.expect_warnings) {
U.AST_Node.warn_function = original_warn_function; U.AST_Node.warn_function = original_warn_function;
@@ -171,8 +177,7 @@ function run_compress_tests() {
expected_warnings: expected_warnings, expected_warnings: expected_warnings,
actual_warnings: actual_warnings, actual_warnings: actual_warnings,
}); });
failures++; return false;
failed_files[file] = 1;
} }
} }
if (test.expect_stdout if (test.expect_stdout
@@ -189,9 +194,8 @@ function run_compress_tests() {
actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR", actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR",
actual: stdout, actual: stdout,
}); });
failures++; return false;
failed_files[file] = 1; }
} else {
stdout = sandbox.run_code(output); stdout = sandbox.run_code(output);
if (!sandbox.same_stdout(test.expect_stdout, stdout)) { 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", { log("!!! failed\n---INPUT---\n{input}\n---EXPECTED {expected_type}---\n{expected}\n---ACTUAL {actual_type}---\n{actual}\n\n", {
@@ -201,16 +205,17 @@ function run_compress_tests() {
actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR", actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR",
actual: stdout, actual: stdout,
}); });
failures++; return false;
failed_files[file] = 1;
}
}
} }
} }
return true;
} }
var tests = parse_test(path.resolve(dir, file)); var tests = parse_test(path.resolve(dir, file));
for (var i in tests) if (tests.hasOwnProperty(i)) { 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){ files.forEach(function(file){