Repository: mishoo/UglifyJS (JavaScript)

Issue:
ufuzz failure
```js
// original code
// (beautified)
var _calls_ = 10, a = 100, b = 10, c = 0;

function f0() {
    c = c + 1;
    var foo_2 = function bar_2() {
        {
            var a_1 = function f1(parseInt_1, bar_1) {
                switch ((c = c + 1) + (typeof bar_2 == "function" && --_calls_ >= 0 && bar_2((c = 1 + c, 
                "number" >> 38..toString() >> ("object", false) > (2 % 1 === ("c" !== -5))), (c = 1 + c, 
                (-0 >>> "", 1 || "object") >>> (22 & Infinity && "undefined" <= -0))))) {
                  default:
                    {
                        var expr5 = (c = 1 + c, ({} ^ [] && 5 - "object") * (38..toString() % -4 % ("foo" & "function")));
                        for (var key5 in expr5) {
                            c = 1 + c;
                            var foo_2 = expr5[key5];
                            c = 1 + c, ("c" || -4) <= -false | (25 >> 38..toString() || true === [ , 0 ][1]);
                        }
                    }
                    return c = 1 + c, (parseInt_1 += ("foo" ^ "b") != -4 + 5) !== -0 + "c" + (bar_2 && (bar_2[c = 1 + c, 
                    bar_2 && (bar_2.undefined >>>= ((-0 !== 5) > -5 - 4) % (24..toString() >>> 5 ^ ("undefined" ^ "b")))] = -3 & "function"));

                  case a++:
                    var b_1;
                    break;

                  case --b + 5:
                    try {
                        c = 1 + c, (foo_2 *= (3 ^ []) >>> (-5 >>> "undefined")) < ((-2 & []) !== (4 & this));
                    } finally {
                    }
                    try {
                        c = 1 + c, ((c = c + 1, "c") <= ("a" == null)) + (-1 << -1 >>> "b" / 5);
                    } catch (foo) {
                    } finally {
                    }
                    break;

                  case (c = c + 1) + (((c = 1 + c, (false === null, 22, -0) - (([ , 0 ].length === 2) - null && (true || undefined))) || 3).toString()[c = 1 + c, 
                    ("object" & 4 ^ (bar_1 && (bar_1.a = 23..toString() % 2))) >>> (-3 < undefined > (true !== -4))] || 1).toString()[bar_2 && typeof bar_2.undefined == "function" && --_calls_ >= 0 && bar_2.undefined((c = 1 + c, 
                    (23..toString() % true, "foo" / "object") << ((true ^ this) <= ([ , 0 ].length === 2) / 0)), "foo")]:
                    if (c = 1 + c, (5 == undefined) + (bar_1 += 3 != []) >= (22 && 5) / (-3, false)) {
                        c = 1 + c, 1 >> "object" >>> undefined - NaN !== ("bar" + "function" | (b_1 += "c" && "foo"));
                    }
                    switch (c = 1 + c, ((NaN, NaN) ^ "undefined" != -2) * ((b_1 && (b_1[c = 1 + c, (b_1 && (b_1.NaN = (c = c + 1, 
                    5) != this >>> 25)) == -(null / "object")] -= NaN >> 3)) ^ (0 ^ -5))) {
                      case c = 1 + c, ("b" / Infinity | "number" != -0) ^ (parseInt_1 && (parseInt_1[c = 1 + c, 
                        (false >= -1 ^ (b_1 && (b_1[c = 1 + c, (("bar" && 2) ^ -2 * "function") & ("foo" == NaN && "object" >>> -2)] += -4 / 0))) >= ("function" % "undefined" == ("a", 
                        {}))] += "undefined" > "foo")) > 24..toString() - this:
                        ;
                        break;

                      case c = 1 + c, (undefined < "undefined") << ("object" ^ null) | (38..toString() || []) >= (Infinity > Infinity):
                        ;
                        break;

                      default:
                        ;

                      case c = 1 + c, (foo_2 && (foo_2.c += 5 || NaN)) >>> (bar_1 && (bar_1[c = 1 + c, ((c = c + 1, 
                        -1) >> void [ , 0 ][1]) / ((3 && this) > (c = c + 1, 4))] = this | "object")) ^ !([] + -0):
                        ;
                        break;
                    }
                    break;
                }
                {
                    var brake16 = 5;
                    while ((--b + /[abc4]/.test(((c = 1 + c, ("number" << 0 === (c = c + 1, "c")) >>> ("function" === 1) + (bar_2 && (bar_2[c = 1 + c, 
                    (Infinity && null) !== (/[a2][^e]+$/ & 23..toString()) ^ ("object" / -1 || false + {})] += -3 || 22))) || b || 5).toString()) || a || 3).toString() && --brake16 > 0) {
                        return;
                        a++ + (typeof b_1 == "function" && --_calls_ >= 0 && b_1("foo"));
                    }
                }
            }((c = c + 1) + (("undefined" == -3) << (1 | []) > (-2 <= 38..toString() <= (NaN >= 22))), false);
        }
    }, bar_2 = (c = c + 1) + (typeof foo_2 == "function" && --_calls_ >= 0 && foo_2());
}

var undefined_1 = f0(1 === 1 ? a : b, (c = c + 1) + [].NaN);

console.log(null, a, b, c, Infinity, NaN, undefined);
```
```js
// uglified code
// (beautified)
var _calls_ = 10, a = 100, b = 10, c = 0;

function f0() {
    c += 1;
    function foo_2() {
        !function(parseInt_1, bar_1) {
            switch ((c += 1) + ("function" == typeof foo_2 && 0 <= --_calls_ && foo_2((c = 1 + c, 
            38..toString()), c = 1 + c))) {
              default:
                var expr5 = (c = 1 + c, ({} ^ [] && NaN) * (38..toString() % -4 % 0));
                for (var key5 in expr5) {
                    var foo_2 = expr5[key5];
                    c = 1 + (c = 1 + c), 38..toString();
                }
                return c = 1 + c, parseInt_1 += !0, foo_2 && (foo_2[c = 1 + c, foo_2 && (foo_2.undefined >>>= !0 % (24..toString() >>> 5 ^ 0))] = 0);

              case a++:
                var b_1;
                break;

              case 5 + --b:
                c = 1 + c, foo_2 *= (3 ^ []) >>> 4294967291;
                try {
                    c = 1 + c, c += 1;
                } catch (foo) {}
                break;

              case (c += 1) + (c = 1 + c, ((-0 - ((2 === [ , 0 ].length) - null && !0) || 3).toString()[c = 1 + c, 
                (0 ^ (bar_1 && (bar_1.a = 23..toString() % 2))) >>> !1] || 1).toString()[foo_2 && "function" == typeof foo_2.undefined && 0 <= --_calls_ && foo_2.undefined((c = 1 + c, 
                23..toString(), NaN << ((!0 ^ this) <= (2 === [ , 0 ].length) / 0)), "foo")]):
                switch (c = 1 + c, !1 + (bar_1 += 3 != []) >= 1 / 0 && (c = 1 + c, b_1 += "foo"), 
                c = 1 + c, -5 ^ (b_1 && (b_1[c = 1 + c, NaN == (b_1 && (b_1.NaN = (c += 1, 5 != this >>> 25)))] -= 0))) {
                  case c = 1 + c, 1 ^ (parseInt_1 && (parseInt_1[c = 1 + c, (!0 ^ (b_1 && (b_1[c = 1 + c, 
                    0] += -1 / 0))) >= (NaN == {})] += !0)) > 24..toString() - this:
                  case c = 1 + c, 0 | !1 <= (38..toString() || []):
                    break;

                  default:
                  case c = 1 + c, (foo_2 && (foo_2.c += 5)) >>> (bar_1 && (bar_1[c = 1 + c, c += 1, 
                    -1 / (this > (c += 1, 4))] = "object" | this)) ^ !([] + -0):
                }
            }
            (--b + /[abc4]/.test((c = 1 + c, ((0 === (c += 1, "c")) >>> !1 + (foo_2 && (foo_2[c = 1 + c, 
            null !== (/[a2][^e]+$/ & 23..toString()) ^ !1 + {}] += -3)) || b || 5).toString())) || a || 3).toString();
        }((c += 1) + (!1 << (1 | []) > (-2 <= 38..toString() <= !1)), !1);
    }
    c += 1, 0 <= --_calls_ && foo_2();
}

var undefined_1 = f0(a, (c += 1) + [].NaN);

console.log(null, a, b, c, 1 / 0, NaN, void 0);
```
```
original result:
null 110 0 101 Infinity NaN undefined

uglified result:
null 101 9 10 Infinity NaN undefined
```
```js
// reduced test case (output will differ)

// (beautified)
function f0() {
    var foo_2 = function bar_2() {
        (function f1() {
            switch (console) {
              default:
                var foo_2 = 0;

              case 0:
                foo_2.c;
            }
            console.log(bar_2);
        })();
    }, bar_2 = typeof foo_2 && foo_2();
}

f0();
// output: function(){}
// 
// minify: 0
// 
// options: {
//   "mangle": false
// }
```
```
minify(options):
{
  "mangle": false
}

Suspicious compress options:
  functions
  reduce_vars
  unused
```


Discussion:

Patch:
diff --git a/lib/compress.js b/lib/compress.js
index cc2d1ecbd61..9ddbfa43a04 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -4360,7 +4360,7 @@ merge(Compressor.prototype, {
                         if (def.value && indexOf_assign(sym, def) < 0) {
                             def.value = def.value.drop_side_effect_free(compressor);
                         }
-                        var var_defs = var_defs_by_id.get(sym.id);
+                        var old_def, var_defs = var_defs_by_id.get(sym.id);
                         if (!def.value) {
                             if (var_defs.length > 1) {
                                 AST_Node.warn("Dropping duplicated declaration of variable {name} [{file}:{line},{col}]", template(def.name));
@@ -4375,23 +4375,23 @@ merge(Compressor.prototype, {
                             && sym.assignments == 0
                             && def.value === def.name.fixed_value()
                             && def.value instanceof AST_Function
-                            && !(def.value.name && def.value.name.definition().assignments)
+                            && (!def.value.name || (old_def = def.value.name.definition()).assignments == 0
+                                && (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)) {
                             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);
                             var name_def = def.name.scope.resolve().def_function(defun.name);
-                            if (def.value.name) {
-                                var old_def = def.value.name.definition();
-                                def.value.walk(new TreeWalker(function(node) {
-                                    if (node instanceof AST_SymbolRef && node.definition() === old_def) {
-                                        node.name = name_def.name;
-                                        node.thedef = name_def;
-                                        node.reference({});
-                                    }
-                                }));
-                            }
+                            if (old_def) def.value.walk(new TreeWalker(function(node) {
+                                if (node instanceof AST_SymbolRef && node.definition() === old_def) {
+                                    node.name = name_def.name;
+                                    node.thedef = name_def;
+                                    node.reference({});
+                                }
+                            }));
                             body.push(defun);
                         } else {
                             if (var_defs.length > 1 && sym.orig.indexOf(def.name) > sym.eliminated) {


Test patch:
diff --git a/test/compress/functions.js b/test/compress/functions.js
index 7ad6ff03229..9607c59362d 100644
--- a/test/compress/functions.js
+++ b/test/compress/functions.js
@@ -4705,3 +4705,44 @@ issue_3911: {
     }
     expect_stdout: "PASS"
 }
+
+issue_3929: {
+    options = {
+        functions: true,
+        reduce_vars: true,
+        unused: true,
+    }
+    input: {
+        (function() {
+            var abc = function f() {
+                (function() {
+                    switch (f) {
+                    default:
+                        var abc = 0;
+                    case 0:
+                        abc.p;
+                    }
+                    console.log(typeof f);
+                })();
+            };
+            typeof abc && abc();
+        })();
+    }
+    expect: {
+        (function() {
+            var abc = function f() {
+                (function() {
+                    switch (f) {
+                    default:
+                        var abc = 0;
+                    case 0:
+                        abc.p;
+                    }
+                    console.log(typeof f);
+                })();
+            };
+            typeof abc && abc();
+        })();
+    }
+    expect_stdout: "function"
+}