From ac26993b5a1546e790d93603d0d7a05740566b07 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 26 Feb 2021 20:16:14 +0000 Subject: [PATCH] fix corner cases with block-scoped functions (#4695) --- lib/compress.js | 36 ++++++---- lib/scope.js | 7 ++ test/compress/const.js | 44 ++++++++++++ test/compress/dead-code.js | 13 ++-- test/compress/functions.js | 144 ++++++++++++++++++++++++++++++++++++- test/compress/let.js | 47 ++++++++++++ 6 files changed, 271 insertions(+), 20 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 16d86d90..0e698804 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1509,11 +1509,22 @@ merge(Compressor.prototype, { return stat instanceof AST_Const || stat instanceof AST_DefClass || stat instanceof AST_Let; } + function safe_to_trim(stat) { + if (stat instanceof AST_LambdaDefinition) { + var def = stat.name.definition(); + return def.scope === stat.name.scope || all(def.references, function(ref) { + var scope = ref.scope; + do { + if (scope === stat.name.scope) return true; + } while (scope = scope.parent_scope); + }); + } + return !is_lexical_definition(stat); + } + function as_statement_array(thing) { if (thing === null) return []; - if (thing instanceof AST_BlockStatement) return all(thing.body, function(stat) { - return !is_lexical_definition(stat); - }) ? thing.body : [ thing ]; + if (thing instanceof AST_BlockStatement) return all(thing.body, safe_to_trim) ? thing.body : [ thing ]; if (thing instanceof AST_EmptyStatement) return []; if (is_statement(thing)) return [ thing ]; throw new Error("Can't convert thing to statement array"); @@ -2732,9 +2743,7 @@ merge(Compressor.prototype, { for (var i = 0; i < statements.length;) { var stat = statements[i]; if (stat instanceof AST_BlockStatement) { - if (all(stat.body, function(stat) { - return !is_lexical_definition(stat); - })) { + if (all(stat.body, safe_to_trim)) { CHANGED = true; eliminate_spurious_blocks(stat.body); [].splice.apply(statements, [i, 1].concat(stat.body)); @@ -2972,15 +2981,16 @@ merge(Compressor.prototype, { } function extract_functions() { - var tail = statements.slice(i + 1); - statements.length = i + 1; - return tail.filter(function(stat) { - if (stat instanceof AST_Defun) { - statements.push(stat); + var defuns = []; + var tail = statements.splice(i + 1).filter(function(stat) { + if (stat instanceof AST_LambdaDefinition) { + defuns.push(stat); return false; } return true; }); + [].push.apply(all(tail, safe_to_trim) ? statements : tail, defuns); + return tail; } function as_statement_array_with_return(node, ab) { @@ -3439,7 +3449,7 @@ merge(Compressor.prototype, { function push(node) { if (block) { block.push(node); - if (is_lexical_definition(node)) block.required = true; + if (!safe_to_trim(node)) block.required = true; } else { target.push(node); } @@ -5118,7 +5128,7 @@ merge(Compressor.prototype, { return in_list ? List.skip : make_node(AST_EmptyStatement, node); case 1: var stat = node.body[0]; - if (is_lexical_definition(stat)) return node; + if (!safe_to_trim(stat)) return node; if (parent instanceof AST_IterationStatement && stat instanceof AST_LambdaDefinition) return node; return stat; } diff --git a/lib/scope.js b/lib/scope.js index 799b0dc6..600a6869 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -327,6 +327,13 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) { self.uses_eval = true; } } + if (sym.init instanceof AST_LambdaDefinition && sym.scope !== sym.init.name.scope) { + var scope = node.scope; + do { + if (scope === sym.init.name.scope) break; + } while (scope = scope.parent_scope); + if (!scope) sym.init = undefined; + } node.thedef = sym; node.reference(options); return true; diff --git a/test/compress/const.js b/test/compress/const.js index be49e845..4306fff2 100644 --- a/test/compress/const.js +++ b/test/compress/const.js @@ -1454,3 +1454,47 @@ issue_4689: { expect_stdout: "PASS" node_version: ">=4" } + +issue_4691: { + options = { + if_return: true, + toplevel: true, + } + input: { + function A() {} + A.prototype.f = function() { + if (!this) + return; + const a = "PA"; + function g(b) { + h(a + b); + } + [ "SS" ].forEach(function(c) { + g(c); + }); + }; + function h(d) { + console.log(d); + } + new A().f(); + } + expect: { + function A() {} + A.prototype.f = function() { + if (this) { + const a = "PA"; + [ "SS" ].forEach(function(c) { + g(c); + }); + function g(b) { + h(a + b); + } + } + }; + function h(d) { + console.log(d); + } + new A().f(); + } + expect_stdout: "PASS" +} diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index 1720a0dc..9101869d 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -53,8 +53,10 @@ dead_code_2_should_warn: { g(); x = 10; throw new Error("foo"); - var x; - function g(){}; + { + var x; + function g(){}; + } } f(); } @@ -62,7 +64,6 @@ dead_code_2_should_warn: { expect_warnings: [ "WARN: Dropping unreachable code [test/compress/dead-code.js:8,12]", ] - node_version: "<=4" } dead_code_constant_boolean_should_warn_more: { @@ -88,8 +89,10 @@ dead_code_constant_boolean_should_warn_more: { bar(); } expect: { - var foo; - function bar() {} + { + var foo; + function bar() {} + } // nothing for the while // as for the for, it should keep: var x = 10, y; diff --git a/test/compress/functions.js b/test/compress/functions.js index fe9307ca..44e41dff 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -5018,9 +5018,12 @@ catch_no_argname: { try { throw a; } catch { - console.log(a, a, a); + function g() { + return a; + } + console.log(a, a, g()); } - console.log(a, a, a); + console.log(a, a, g()); } expect_stdout: [ "PASS PASS PASS", @@ -5558,3 +5561,140 @@ issue_4659_3: { } expect_stdout: "1" } + +block_scope_1: { + input: { + console.log(typeof f); + function f() {} + } + expect: { + console.log(typeof f); + function f() {} + } + expect_stdout: "function" +} + +block_scope_1_compress: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + typeofs: true, + unused: true, + } + input: { + console.log(typeof f); + function f() {} + } + expect: { + console.log("function"); + } + expect_stdout: "function" +} + +block_scope_2: { + input: { + { + console.log(typeof f); + } + function f() {} + } + expect: { + console.log(typeof f); + function f() {} + } + expect_stdout: "function" +} + +block_scope_2_compress: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + typeofs: true, + unused: true, + } + input: { + { + console.log(typeof f); + } + function f() {} + } + expect: { + console.log("function"); + } + expect_stdout: "function" +} + +block_scope_3: { + input: { + console.log(typeof f); + { + function f() {} + } + } + expect: { + console.log(typeof f); + { + function f() {} + } + } + expect_stdout: true +} + +block_scope_3_compress: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + typeofs: true, + unused: true, + } + input: { + console.log(typeof f); + { + function f() {} + } + } + expect: { + console.log(typeof f); + { + function f() {} + } + } + expect_stdout: true +} + +block_scope_4: { + input: { + { + console.log(typeof f); + function f() {} + } + } + expect: { + console.log(typeof f); + function f() {} + } + expect_stdout: "function" +} + +block_scope_4_compress: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + typeofs: true, + unused: true, + } + input: { + { + console.log(typeof f); + function f() {} + } + } + expect: { + console.log("function"); + } + expect_stdout: "function" +} diff --git a/test/compress/let.js b/test/compress/let.js index b5005ebf..04002e9a 100644 --- a/test/compress/let.js +++ b/test/compress/let.js @@ -1357,3 +1357,50 @@ issue_4689: { expect_stdout: "PASS" node_version: ">=4" } + +issue_4691: { + options = { + if_return: true, + toplevel: true, + } + input: { + "use strict"; + function A() {} + A.prototype.f = function() { + if (!this) + return; + let a = "PA"; + function g(b) { + h(a + b); + } + [ "SS" ].forEach(function(c) { + g(c); + }); + }; + function h(d) { + console.log(d); + } + new A().f(); + } + expect: { + "use strict"; + function A() {} + A.prototype.f = function() { + if (this) { + let a = "PA"; + [ "SS" ].forEach(function(c) { + g(c); + }); + function g(b) { + h(a + b); + } + } + }; + function h(d) { + console.log(d); + } + new A().f(); + } + expect_stdout: "PASS" + node_version: ">=4" +} -- 2.34.1