Repository: mishoo/UglifyJS (JavaScript)

Issue:
ufuzz failure
```js
// original code
// (beautified)
var _calls_ = 10, a = 100, b = 10, c = 0;

function f0(a, b_2, bar) {
    {
        return ~((b_2 = 1 === "number" & (Infinity && -0)) != (b_2 && (b_2.Infinity = true >= this > (b_2 += "number" && "object"))));
    }
    try {
        {
            var brake3 = 5;
            L19897: while (a-- && --brake3 > 0) {
                var brake4 = 5;
                while (~a && --brake4 > 0) {
                    return a++ + (b_2 && b_2[--b + {
                        3: {
                            "-2": (c = 1 + c, (c = c + 1, NaN & 1) >> ((c = c + 1, -0) >> ("bar" >= -2))),
                            in: (c = 1 + c, -1 != "" !== (3 | 38..toString()) & (b_2 && (b_2.Infinity += 25 > "b")) >> ("" !== "undefined")),
                            "\t": (c = 1 + c, ("" == ([ , 0 ].length === 2)) >>> [] - 3 <= (b_2 += Infinity % "a") - (0 === "function")),
                            c: (c = 1 + c, b_2 && (b_2.undefined += (b_2 = -"a" | false >= 1) !== ("bar" === "undefined") / (3 != 3)))
                        },
                        undefined: 1 === 1 ? a : b,
                        "\t": --b + b_2
                    }.b]);
                }
            }
        }
    } catch (parseInt_1) {
        {
            var brake6 = 5;
            do {
                try {
                    c = c + 1;
                } finally {
                    switch (b >>>= a) {
                      default:
                        L19898: {
                            c = 1 + c, Infinity * 0 + (-4 || true) >> delete (Infinity - 38..toString());
                        }

                      case (c = c + 1) + ((c = c + 1) + /[abc4]/g.exec((b_2 && typeof b_2.null == "function" && --_calls_ >= 0 && b_2.null((c = 1 + c, 
                        (null >= 3 ^ [] === 25) >= (5 - "foo" == (38..toString() & "object")))) || b || 5).toString()) ? a++ + ((0 === 1 ? a : b) ? typeof (c = 1 + c, 
                        (-0 && 4) * (Infinity, 38..toString()) + ("b" ^ 23..toString()) / (NaN === 3)) : --b + (b_2 && typeof b_2.in == "function" && --_calls_ >= 0 && b_2.in())) : ((c = c + 1) + /[abc4]/g.exec(((c = 1 + c, 
                        (this ^ undefined) == (24..toString() && 0) & (parseInt_1 && (parseInt_1[--b + ((c = 1 + c, 
                        ([ , 0 ].length === 2 === undefined === (b_2 = -1 <= -4)) % ((false, -3) == (4 && 25))) || 6).toString()[c = 1 + c, 
                        ~22 < (false <= -2) ^ (this == 1, 5 ^ "b")]] = (parseInt_1 -= "object" & [ , 0 ].length === 2) % (4 && 24..toString())))) || b || 5).toString()) || a || 3).toString()):
                        if ((c = c + 1) + (parseInt_1 && typeof parseInt_1.NaN == "function" && --_calls_ >= 0 && parseInt_1.NaN(this, (c = 1 + c, 
                        ("undefined" && 5) % (true >> NaN) !== ("bar" >>> this ^ ("" ^ true)))))) {
                            try {
                                c = 1 + c, (("undefined" & -5) >= (24..toString() && -2)) << ((c = c + 1, 22) !== {} * 4);
                            } catch (a) {
                            } finally {
                            }
                        }
                        break;

                      case b_2 = a++ + (24..toString() ? typeof f0 == "function" && --_calls_ >= 0 && f0() : typeof f0 == "function" && --_calls_ >= 0 && f0(0, (c = 1 + c, 
                        (b_2 && (b_2.Infinity = "undefined" % -3)) <= 25 + -5 == (c = c + 1, -1 << null)))):
                        var a = (c = c + 1) + ++a;
                        try {
                            c = c + 1;
                        } catch (bar_2) {
                            c = 1 + c, (null == 3) + (3, false) ^ "number" === "function" != 2 - [];
                            c = 1 + c, bar_2 && (bar_2.undefined >>= ((2 === "b") << (-2 && "a")) / (22 * -3 & (c = c + 1, 
                            Infinity)));
                        }
                        break;

                      case --b:
                        break;
                    }
                    {
                        var brake20 = 5;
                        while ([ --b + [ , (c = 1 + c, (-1 == "undefined") / ([ , 0 ].length === 2 & 24..toString()) == false >> -4 >> (1 <= 1)), (c = 1 + c, 
                        c = c + 1, this >> 3, b_2 && (b_2.Infinity = (-1 === -1) + (22 < [ , 0 ][1]))) ].a, --b + typeof (c = 1 + c, 
                        ("" > NaN) % (NaN * null) && (0 !== "") - (-2 | "function")), , parseInt_1, typeof f1 == "function" && --_calls_ >= 0 && f1() ][a++ + (a++ + ((c = 1 + c, 
                        parseInt_1 && (parseInt_1.in >>= [ , 0 ][1] / 1 > (-0 != 5) == ("foo" >> -0 || delete 3))) || a || 3).toString() ? a++ + typeof (c = 1 + c, 
                        (1 % null && -3 < 23..toString()) != ("" + -1 !== false >> [ , 0 ][1])) : a++ + function() {
                        }())] && --brake20 > 0) {
                            for (var brake21 = 5; typeof b_2 == "function" && --_calls_ >= 0 && b_2(-1) && brake21 > 0; --brake21) {
                                switch (--b + --a) {
                                  case typeof a_2 != "symbol":
                                    break;

                                  case {}[c = 1 + c, !"c" > (null == "b") <= (parseInt_1 = "" >> "c" != Infinity * "object")]:
                                    c = 1 + c, (2 > true || (parseInt_1 += 3 === Infinity)) & (23..toString() ^ true) - (b_2 = -5 && "number");
                                    c = 1 + c, (38..toString() === 24..toString(), 1 >> -1) + ("c" === "a" && (c = c + 1, 
                                    false));
                                    break;

                                  default:
                                    c = 1 + c, b_2 && (b_2[b_2 && typeof b_2.NaN == "function" && --_calls_ >= 0 && b_2.NaN(38..toString())] = false % ([ , 0 ].length === 2) === ("a" ^ "bar") != ("undefined" >> "object" || 25 << [ , 0 ][1]));

                                  case --b + b--:
                                    c = 1 + c, (b_2 && (b_2.var += (parseInt_1 && (parseInt_1.Infinity = "a" * 23..toString())) / (4 && true))) % (-5 - "a" - (23..toString() || 1));
                                    break;
                                }
                            }
                        }
                    }
                }
            } while (parseInt_1 && --brake6 > 0);
        }
        c = c + 1;
    } finally {
        var b_2 = b_2 && b_2.undefined;
    }
}

var b = f0(38..toString());

console.log(null, a, b, c, Infinity, NaN, undefined);
```
```js
// uglified code
// (beautified)
console.log(null, 100, -2, 0, 1 / 0, NaN, void 0);


original result:
null 100 -1 0 Infinity NaN undefined

uglified result:
null 100 -2 0 Infinity NaN undefined
```
```js
// reduced test case (output will differ)

// (beautified)
function f0(b_2) {
    return (b_2 = 0) != (b_2 && this > (b_2 += 0));
}

var b = f0();

console.log(b);
// output: false
// 
// minify: true
// 
// options: {
//   "compress": {
//     "passes": 1000000,
//     "unsafe": true
//   },
//   "toplevel": true
// }
```
```
minify(options):
{
  "compress": {
    "passes": 1000000,
    "unsafe": true
  },
  "toplevel": true
}

Suspicious compress options:
  dead_code
  evaluate
  passes
  reduce_vars
  unused

Suspicious options:
  toplevel
```


Discussion:

Patch:
diff --git a/lib/compress.js b/lib/compress.js
index 1b5d1a3a93c..08abde9bfc1 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -858,7 +858,7 @@ merge(Compressor.prototype, {
             d.assignments++;
             var fixed = d.fixed;
             if (!fixed) return;
-            exp.fixed = d.fixed = function() {
+            d.fixed = function() {
                 var value = fixed instanceof AST_Node ? fixed : fixed();
                 return value && make_node(AST_Binary, node, {
                     operator: node.operator.slice(0, -1),
@@ -871,6 +871,13 @@ merge(Compressor.prototype, {
                     })
                 });
             };
+            exp.fixed = node instanceof AST_UnaryPrefix ? d.fixed : function() {
+                var value = fixed instanceof AST_Node ? fixed : fixed();
+                return value && make_node(AST_UnaryPrefix, node, {
+                    operator: "+",
+                    expression: value
+                });
+            };
             if (!safe) return;
             d.references.push(exp);
             mark(tw, d, true);
@@ -3233,6 +3240,12 @@ merge(Compressor.prototype, {
                     && unaryPrefix[this.operator];
             }
         });
+        function modified(sym) {
+            if (!(sym instanceof AST_SymbolRef)) return;
+            sym.definition().references.forEach(function(node) {
+                delete node._eval;
+            });
+        }
         def(AST_Statement, function() {
             throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
         });
@@ -3246,6 +3259,7 @@ merge(Compressor.prototype, {
             if (this.operator != "=") return this;
             var node = this.right;
             var value = node._eval(compressor, ignore_side_effects, cached, depth);
+            modified(this.left);
             return value === node ? this : value;
         });
         def(AST_Sequence, function(compressor, ignore_side_effects, cached, depth) {
@@ -3317,7 +3331,7 @@ merge(Compressor.prototype, {
             if (!non_converting_unary[op]) depth++;
             var v = e._eval(compressor, ignore_side_effects, cached, depth);
             if (v === e) {
-                if (ignore_side_effects && op == "void") return void 0;
+                if (ignore_side_effects && op == "void") return;
                 return this;
             }
             switch (op) {
@@ -3327,7 +3341,7 @@ merge(Compressor.prototype, {
                 // so cannot evaluate reliably
                 if (v instanceof RegExp) return this;
                 return typeof v;
-              case "void": return void v;
+              case "void": return;
               case "~": return ~v;
               case "-": return -v;
               case "+": return +v;
@@ -3335,11 +3349,22 @@ merge(Compressor.prototype, {
               case "--":
                 if (!(e instanceof AST_SymbolRef)) return this;
                 var refs = e.definition().references;
-                if (refs[refs.length - 1] !== e) return this;
-                return HOP(e, "_eval") ? +(op[0] + 1) + +v : v;
+                if (!ignore_side_effects && refs[refs.length - 1] !== e) return this;
+                if (HOP(e, "_eval")) v = +(op[0] + 1) + +v;
+                modified(e);
+                return v;
             }
             return this;
         });
+        def(AST_UnaryPostfix, function(compressor, ignore_side_effects, cached, depth) {
+            var e = this.expression;
+            if (!(e instanceof AST_SymbolRef)) return this;
+            var refs = e.definition().references;
+            if (!ignore_side_effects && refs[refs.length - 1] !== e) return this;
+            var v = e._eval(compressor, ignore_side_effects, cached, depth + 1);
+            modified(e);
+            return v === e ? this : +v;
+        });
         var non_converting_binary = makePredicate("&& || === !==");
         def(AST_Binary, function(compressor, ignore_side_effects, cached, depth) {
             if (!non_converting_binary[this.operator]) depth++;
@@ -3514,14 +3539,14 @@ merge(Compressor.prototype, {
                             }
                             if (node instanceof AST_Scope && node !== fn) return true;
                         }));
-                        if (!found) return void 0;
+                        if (!found) return;
                     }
                     return this;
                 }
                 var args = eval_args(this.args);
-                if (!args) return this;
-                if (!stat.value) return undefined;
-                if (!all(fn.argnames, function(sym, i) {
+                if (!args && !ignore_side_effects) return this;
+                if (!stat.value) return;
+                if (args && !all(fn.argnames, function(sym, i) {
                     var value = args[i];
                     var def = sym.definition();
                     if (def.orig[def.orig.length - 1] !== sym) return false;
@@ -3532,7 +3557,7 @@ merge(Compressor.prototype, {
                         cached.push(node);
                     });
                     return true;
-                })) return this;
+                }) && !ignore_side_effects) return this;
                 fn.evaluating = true;
                 var val = stat.value._eval(compressor, ignore_side_effects, cached, depth);
                 delete fn.evaluating;


Test patch:
diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js
index a46d38aa2e0..a4bee8cc279 100644
--- a/test/compress/evaluate.js
+++ b/test/compress/evaluate.js
@@ -2326,3 +2326,46 @@ void_returns_recursive: {
         "undefined",
     ]
 }
+
+issue_3878_1: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        var b = function(a) {
+            return (a = 0) == (a && this > (a += 0));
+        }();
+        console.log(b ? "PASS" : "FAIL");
+    }
+    expect: {
+        var b = function(a) {
+            return (a = 0) == (a && this > (a += 0));
+        }();
+        console.log(b ? "PASS" : "FAIL");
+    }
+    expect_stdout: "PASS"
+}
+
+issue_3878_2: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        toplevel: true,
+    }
+    input: {
+        var a = "foo";
+        a++ + a;
+        a && a;
+        console.log(a);
+    }
+    expect: {
+        var a = "foo";
+        a++ + a;
+        a;
+        console.log(a);
+    }
+    expect_stdout: "NaN"
+}