Repository: mishoo/UglifyJS (JavaScript)

Issue:
ufuzz failure
```js
// original code
// (beautified)
var _calls_ = 10, a = 100, b = 10, c = 0;

function f0(b, a) {
    try {
        {
            var brake2 = 5;
            while (--b + (b += a) && --brake2 > 0) {
                var bar_2 = function() {
                    c = c + 1;
                };
            }
        }
    } catch (bar_2) {
        return {
            set foo(NaN) {
                {
                    var brake6 = 5;
                    L14563: while (~(((this, [ , 0 ].length === 2) ^ 0 >>> -2) % ((this === 0) > ("undefined" !== 5))) && --brake6 > 0) {
                        try {
                        } finally {
                            c = 1 + c, this ^ -0 | (23..toString() && 1) && (24..toString() >= Infinity) % (38..toString() & "number");
                            c = 1 + c, ~(false < "foo") != ("bar" >> 2) * (-1 <= ([ , 0 ].length === 2));
                        }
                    }
                }
                this.undefined = -4 == 0 != (c = c + 1, "b") == (24..toString(), "a") > (NaN >= "bar") === (("object" === "number") >= ("" ^ "c") != ({} >= 3) / ("object", 
                23..toString())) & (1 / ([ , 0 ].length === 2) <= (null | [ , 0 ].length === 2) ^ (bar_2 && (bar_2.undefined += (3 ^ [ , 0 ][1]) >>> ("function" >= true)))) + ((c = c + 1, 
                true) > (-1 <= Infinity)) / ("bar" + [ , 0 ][1] >> (-1 | 3));
            },
            var: [ --b + (1 === 1 ? a : b), b--, a++ + !function b() {
                c = 1 + c, (bar_2 && (bar_2[c = 1 + c, (+"number" && 4 ^ "function") ^ (null <= "c" ^ 5 < undefined)] |= "number" << "b")) % (c = c + 1, 
                "number") * (NaN * Infinity - ([ , 0 ][1] !== "object"));
            }(), {
                c: (c = 1 + c, 24..toString() >>> -5 >= (null && 23..toString()) === (5 ^ "c") > 23..toString() / 5),
                "": (c = 1 + c, (bar_2 += 23..toString() <= "undefined" <= 25 / -3) & (bar_2 && (bar_2.null = 23..toString() - -4)) > ("bar" && 38..toString())),
                1.5: (c = 1 + c, bar_2 = ((-2 & -5) <= 25 * "function") >>> (NaN & -4 | (bar_2 && (bar_2.var = "bar" && NaN)))),
                var: (c = 1 + c, (true > -1) >>> (false > -5) != [ , 0 ][1] / 25 % (-5 << 38..toString())),
                NaN: (c = 1 + c, (c = c + 1, [ , 0 ].length === 2) > ([ , 0 ][1] && -5) <= (-1 !== {} != (5, 
                5)))
            } ],
            a: bar_2 && typeof bar_2.NaN == "function" && --_calls_ >= 0 && bar_2.NaN(25),
            c: a++ + (b /= a),
            NaN: (c = c + 1) + -b
        }[a++ + (0 === 1 ? a : b)];
        {
            var expr12 = typeof f2 == "function" && --_calls_ >= 0 && f2(-3, (c = c + 1) + typeof 25, /[a2][^e]+$/);
            for (var key12 in expr12) {
                c = 1 + c;
                var bar_2_2 = expr12[key12];
                var b;
            }
        }
    }
    return -((2 ^ [] | -5 < [ , 0 ][1]) != (bar_2 = (1 == -5) / ([] >> true)));
}

var b = f0(NaN);

console.log(null, a, b, c, Infinity, NaN, undefined);
```
```js
// uglified code
//
var r=10,e=0,n=function(n,a){try{for(var N=5;--n+(n+=a)&&0<--N;)function t(){e+=1}}catch(t){return{set foo(n){for(var a=5;~((2===[,0].length^0)%(!0<(0===this)))&&0<--a;)e=1+(1+e);this.undefined=0!=(e+=1,"b")=="bar"<=n<"a"==1&(1/(2===[,0].length)<=(null|2===[,0].length)^(t&&(t.undefined+=3)))+(e+=1,NaN)},var:[--n+a,n--,a+++(e=1+e,t&&(t[e=1+e,0]|=0),!void(e+=1)),{c:!0,"":(e=1+(1+e),(t+=!1)&"38"<(t&&(t.null=27))),1.5:(e=1+e,t=!1>>>(0|(t&&(t.var=NaN)))),var:!1,NaN:(e=1+(1+e),e+=1,0<(2===[,0].length)<=!0)}],a:t&&"function"==typeof t.NaN&&0<=--r&&t.NaN(25),c:a+++(n/=a),NaN:(e+=1)+-n}[a+++n]}return-1}(NaN);console.log(null,100,n,e,1/0,NaN,void 0);
```
```
original result:
null 100 -1 0 Infinity NaN undefined

uglified result:
evalmachine.<anonymous>:1
(function(){var r=10,e=0,n=function(n,a){try{for(var N=5;--n+(n+=a)&&0<--N;)function t(){e+=1}}catch(t){return{set foo(n){for(var a=5;~((2===[,0].length^0)%(!0<(0===this)))&&0<--a;)e=1+(1+e);this.undefined=0!=(e+=1,"b")=="bar"<=n<"a"==1&(1/(2===[,0].length)<=(null|2===[,0].length)^(t&&(t.undefined+=3)))+(e+=1,NaN)},var:[--n+a,n--,a+++(e=1+e,t&&(t[e=1+e,0]|=0),!void(e+=1)),{c:!0,"":(e=1+(1+e),(t+=!1)&"38"<(t&&(t.null=27))),1.5:(e=1+e,t=!1>>>(0|(t&&(t.var=NaN)))),var:!1,NaN:(e=1+(1+e),e+=1,0<(2===[,0].length)<=!0)}],a:t&&"function"==typeof t.NaN&&0<=--r&&t.NaN(25),c:a+++(n/=a),NaN:(e+=1)+-n}[a+++n]}return-1}(NaN);console.log(null,100,n,e,1/0,NaN,void 0);})()
                                                                            ^^^^^^^^

SyntaxError: In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement.
    at createScript (vm.js:80:10)
    at Object.runInContext (vm.js:119:10)
    at Object.exports.run_code (D:\a\UglifyJS\UglifyJS\test\sandbox.js:65:12)
    at D:\a\UglifyJS\UglifyJS\test\ufuzz\index.js:1283:37
    at Array.forEach (<anonymous>)
    at Object.<anonymous> (D:\a\UglifyJS\UglifyJS\test\ufuzz\index.js:1275:51)
    at Module._compile (module.js:653:30)
    at Object.Module._extensions..js (module.js:664:10)
    at Module.load (module.js:566:32)
    at tryModuleLoad (module.js:506:12)
```
```js
// reduced test case (output will differ)

// (beautified)
function f0() {
    try {
        while (1) {
            var bar_2 = function() {};
        }
    } catch (bar_2) {
        return {
            set foo(NaN) {
                bar_2.undefined;
            }
        };
    }
}

var b = f0;

0(b);
// output: TypeError: 0 is not a function
// minify: SyntaxError: In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement.
// options: {
//   "compress": {
//     "passes": 1000000,
//     "unsafe": true
//   },
//   "toplevel": true,
//   "validate": true
// }
```
```
minify(options):
{
  "compress": {
    "passes": 1000000,
    "unsafe": true
  },
  "toplevel": true
}

Suspicious compress options:
  dead_code
  functions
  passes
  reduce_vars
  unused
```


Discussion:

Patch:
diff --git a/lib/compress.js b/lib/compress.js
index 513528e7b6e..a7e5eaca9e3 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -4851,8 +4851,8 @@ merge(Compressor.prototype, {
                                 && (old_def.name == def.name.name || all(old_def.references, function(ref) {
                                     return ref.scope.find_variable(def.name) === def.name.definition();
                                 })))
-                            && can_rename(def.value, def.name.name)
-                            && (!compressor.has_directive("use strict") || parent instanceof AST_Scope)) {
+                            && can_declare_defun()
+                            && can_rename(def.value, def.name.name)) {
                             AST_Node.warn("Declaring {name} as function [{file}:{line},{col}]", template(def.name));
                             var defun = make_node(AST_Defun, def, def.value);
                             defun.name = make_node(AST_SymbolDefun, def.name, def.name);
@@ -4908,6 +4908,13 @@ merge(Compressor.prototype, {
                         var def = fn.variables.get(name);
                         return !def || fn.name && def === fn.name.definition();
                     }
+
+                    function can_declare_defun() {
+                        if (compressor.has_directive("use strict")) return parent instanceof AST_Scope;
+                        return parent instanceof AST_Block
+                            || parent instanceof AST_For && parent.init === node
+                            || parent instanceof AST_If;
+                    }
                 });
                 switch (head.length) {
                   case 0:


Test patch:
diff --git a/test/compress/functions.js b/test/compress/functions.js
index 13368169058..745de9dbdf3 100644
--- a/test/compress/functions.js
+++ b/test/compress/functions.js
@@ -4892,3 +4892,71 @@ direct_inline_catch_redefined: {
     }
     expect_stdout: true
 }
+
+issue_4171_1: {
+    options = {
+        functions: true,
+        reduce_vars: true,
+        unused: true,
+    }
+    input: {
+        console.log(function(a) {
+            try {
+                while (a)
+                    var e = function() {};
+            } catch (e) {
+                return function() {
+                    return e;
+                };
+            }
+        }(!console));
+    }
+    expect: {
+        console.log(function(a) {
+            try {
+                while (a)
+                    var e = function() {};
+            } catch (e) {
+                return function() {
+                    return e;
+                };
+            }
+        }(!console));
+    }
+    expect_stdout: "undefined"
+}
+
+issue_4171_2: {
+    options = {
+        functions: true,
+        reduce_vars: true,
+        unused: true,
+    }
+    input: {
+        console.log(function(a) {
+            try {
+                while (a);
+            } catch (e) {
+                return function() {
+                    return e;
+                };
+            } finally {
+                var e = function() {};
+            }
+        }(!console));
+    }
+    expect: {
+        console.log(function(a) {
+            try {
+                while (a);
+            } catch (e) {
+                return function() {
+                    return e;
+                };
+            } finally {
+                function e() {}
+            }
+        }(!console));
+    }
+    expect_stdout: "undefined"
+}