Repository: mishoo/UglifyJS (JavaScript)

Issue:
ufuzz failure
```js
// original code
// (beautified)
var _calls_ = 10, a = 100, b = 10, c = 0;

function f0(a_2) {
    async function f1(bar_2, a_2_1 = --b + ([ , 0 ].length === 2), ...foo) {
        try {
        } catch (a) {
            c = c + 1;
        } finally {
            if (/[abc4]/g.exec(((c = 1 + c, a_2 = (23..toString() ^ "c") < ((-42n).toString() | -2) <= (bar_2 && (bar_2.value = -2 * "number" % (a_2 = 0 / 3)))) || b || 5).toString())) {
                var expr7 = (c = 1 + c, "bar" != 4 && (a_2 && (a_2.undefined = "b" | {})) || [ , 0 ][1] % -3 << ([ , 0 ][1] || this));
                for (var key7 in expr7) {
                    c = 1 + c;
                    var foo_2 = expr7[key7];
                    c = 1 + c, (-5 ^ -3) << (a_2_1 = -4 >> -3) ?? (this ?? "number") == (this ^ "bar");
                }
            } else {
                var Infinity;
            }
            {
                var brake10 = 5;
                while (--b + a_2 && --brake10 > 0) {
                    c = c + 1;
                }
            }
        }
        var arguments_2 = a++ + ++a, b_1 = await (typeof f2 == "function" && --_calls_ >= 0 && f2());
    }
    var a = f1(3, Infinity, "a");
    function f2(bar) {
        try {
            {
                return typeof bar == "function" && --_calls_ >= 0 && bar(24..toString(), Infinity, "undefined");
            }
        } catch (a_2) {
            c = c + 1;
            for (var brake16 = 5; a++ + (1 === 1 ? a : b) && brake16 > 0; --brake16) {
                try {
                    c = 1 + c, bar && (bar.done = "object" + "object" & /[a2][^e]+$/ > undefined), 
                    (null, false) / ("number" * "object");
                } catch (a_2_2) {
                }
            }
        } finally {
            c = c + 1;
            try {
                {
                    var brake21 = 5;
                    while ((c = 1 + c, (a && (a[function() {
                    }] >>>= (undefined !== 4) * (false / "number"))) > ((a_2 = -4 !== "function") <= (-1 & -5))) && --brake21 > 0) {
                        c = 1 + c, (Number(0xdeadn << 16n | 0xbeefn) ?? -2) * ~2 << ((bar && ([ bar[c = 1 + c, 
                        (bar && (bar[c = 1 + c, (1 == 1 ^ 22 === 3) << (2 ^ true ?? -5 / "foo")] = 22 ^ Infinity)) ^ ~38..toString() && false == "function" | 38..toString() * 2] ] = [ null ^ 1 ])) & (bar && (bar.a = 22 * 1)));
                    }
                }
            } finally {
                c = 1 + c, c = c + 1, [], c = c + 1, "undefined", 3 >> "number" < (25 >= Infinity);
                c = 1 + c, (-5 && 25) / (true / -3) | (-0 % NaN && "bar" < 4);
                c = 1 + c, void [ , 0 ][1] / ("bar" == "c") | -22 < (3 ^ 38..toString());
                c = 1 + c, (bar && (bar.in += (undefined >= false) + (/[a2][^e]+$/ < /[a2][^e]+$/))) !== (this >> 4 || 0 / "c");
            }
        }
        {
            return /[abc4]/g.exec((a || b || 5).toString());
        }
    }
    function f3() {
        function f4() {
            for (var brake28 = 5; (c = 1 + c, "bar" > [ , 0 ][1] !== (a && (a.get = -5 ^ [ , 0 ].length === 2)) | ("foo" ^ 0, 
            a_2 = 25 / -0)) && brake28 > 0; --brake28) {
                c = 1 + c, ("a" >> /[a2][^e]+$/ == (a_2 = "bar" * "b")) >> (-1 ^ "number") ** (this << 2);
            }
            switch (c = 1 + c, ("b" >>> "bar" === 4 + 25) < ((a_2 && ([ a_2.async ] = [ "foo" * {} ])) != (3 === -4))) {
              case c = 1 + c, (a && (a[c = 1 + c, (22 ?? "function") >> (1 | 1) !== -4 << -0 <= (-1 >= "number")] = -5 << [ , 0 ][1])) > ("c" < "a") <= ("bar" + -4 | "" == "c"):
                ;

              case c = 1 + c, (false ^ Infinity) % (this >>> true) | [ , 0 ][1] / null === ("function" & null):
                ;
                break;

              case c = 1 + c, (null < -4) * (a_2 && (a_2.foo <<= NaN >= this)) * ("number" + "object" << (2 != 2)):
                ;
                break;

              case c = 1 + c, ([ , 0 ][1] % 23..toString() >>> ("object" && undefined)) + (c = c + 1, 
                25) * (5 ^ 23..toString()):
                ;
                break;
            }
        }
        var let_2 = f4(a++, --b);
    }
    var bar_2 = f3(38..toString());
}

var arguments_2 = f0(--b + typeof (typeof a == "function" && --_calls_ >= 0 && a()), 2, delete ((-0 ^ "foo") >> (undefined !== ([ , 0 ].length === 2)) << (a && (a.NaN += 24..toString() < 3 != 4 % 24..toString()))));

console.log(null, a, b, c, Infinity, NaN, undefined);
```
```js
// uglified code
// (beautified)
var _calls_ = 10, a = 100, b = 10, c = 0;

function f0(o) {
    var r = async function() {
        if (/[abc4]/g.exec((c = 1 + c, ((o = 23 < ("" + -42n | -2) <= NaN % (o = 0)) || b || 5).toString()))) {
            for (var a in c = 1 + c, o && (o.undefined = 0), 0 << this) {
                c = 1 + (c = 1 + c);
            }
        }
        for (var t = 5; --b + o && 0 < --t; ) {
            c += 1;
        }
        if (r++, ++r, 0 <= --_calls_) {
            try {
                return void await 0;
            } catch (a) {
                c += 1;
                for (var n = 5; r++ + r && 0 < n; --n) {
                    try {
                        c = 1 + c;
                    } catch (c) {}
                }
            } finally {
                c += 1;
                try {
                    for (var i = 5; c = 1 + c, (r && (r[function() {}] >>>= NaN)) > ((o = !0) <= -5) && 0 < --i; ) {
                        c = 1 + c;
                    }
                } finally {
                    c = 1 + (c = 1 + (c = 1 + (c = (c = 1 + c) + 1 + 1)));
                }
            }
            await /[abc4]/g.exec((r || b || 5).toString());
        }
    }();
    !function() {
        for (var a = 5; c = 1 + c, !1 !== (r && (r.get = -5 ^ 2 === [ , 0 ].length)) | (o = -1 / 0) && 0 < a; --a) {
            c = 1 + c, o = NaN;
        }
        switch (c = 1 + c, !1 < (0 != (o && ([ o.async ] = [ NaN ])))) {
          case c = 1 + c, !1 < (r && (r[c = 1 + c, !0] = -5)) <= 0:
          case c = 1 + c, 0 % (this >>> !0) | !1:
          case c = 1 + c, !1 * (o && (o.foo <<= this <= NaN)) * 0:
          case c = 1 + c, 0 + 18 * (c += 1, 25):
        }
    }((r++, --b));
}

var arguments_2 = f0(--b + typeof ("function" == typeof a && 0 <= --_calls_ && a()), 2, (a && (a.NaN += !0), 
!0));

console.log(null, a, b, c, 1 / 0, NaN, void 0);
```
```
original result:
null 100 3 26 Infinity NaN undefined

uglified result:
null 100 3 18 Infinity NaN undefined
```
```js
// reduced test case (output will differ)

// (beautified)
function f0() {
    async function f1() {
        await f2();
    }
    f1();
    function f2() {
        try {
            return 0;
        } finally {
            console.log();
        }
    }
}

f0();

console.log(undefined);
// output: 
// undefined
// 
// minify: undefined
// 
// 
// options: {
//   "compress": {
//     "passes": 1000000,
//     "sequences": 1000000,
//     "unsafe": true,
//     "unsafe_Function": true,
//     "unsafe_math": true,
//     "unsafe_proto": true,
//     "unsafe_regexp": true
//   },
//   "output": {
//     "v8": true
//   },
//   "validate": true
// }
```
```
minify(options):
{
  "compress": {
    "passes": 1000000,
    "sequences": 1000000,
    "unsafe": true,
    "unsafe_Function": true,
    "unsafe_math": true,
    "unsafe_proto": true,
    "unsafe_regexp": true
  },
  "output": {
    "v8": true
  }
}

Suspicious compress options:
  evaluate
  inline
  passes
  reduce_funcs
  reduce_vars
  side_effects
  unused
```


Discussion:

Patch:
diff --git a/lib/compress.js b/lib/compress.js
index d8160222227..9b28bc7d7ce 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -13475,7 +13475,7 @@ Compressor.prototype.compress = function(node) {
         def(AST_Assign, noop);
         def(AST_Await, function(compressor, scope, no_return, in_loop) {
             var self = this;
-            var inlined = sync(self.expression).try_inline(compressor, scope, no_return, in_loop);
+            var inlined = self.expression.try_inline(compressor, scope, no_return, in_loop, true);
             if (!inlined) return;
             if (!no_return) scan_local_returns(inlined, function(node) {
                 node.in_bool = false;
@@ -13490,35 +13490,13 @@ Compressor.prototype.compress = function(node) {
                     body: make_node(AST_Await, self, { expression: make_node(AST_Number, self, { value: 0 })}),
                 }) ],
             });
-
-            function sync(node) {
-                if (!no_return) return node;
-                if (node.TYPE != "Call") return node;
-                var fn = node.expression;
-                switch (fn.CTOR) {
-                  case AST_AsyncArrow:
-                    fn = make_node(AST_Arrow, fn, fn);
-                    break;
-                  case AST_AsyncFunction:
-                    fn = make_node(AST_Function, fn, fn);
-                    break;
-                  case AST_AsyncGeneratorFunction:
-                    fn = make_node(AST_GeneratorFunction, fn, fn);
-                    break;
-                  default:
-                    return node;
-                }
-                node = node.clone();
-                node.expression = fn;
-                return node;
-            }
         });
-        def(AST_Binary, function(compressor, scope, no_return, in_loop) {
+        def(AST_Binary, function(compressor, scope, no_return, in_loop, in_await) {
             if (no_return === undefined) return;
             var self = this;
             var op = self.operator;
             if (!lazy_op[op]) return;
-            var inlined = self.right.try_inline(compressor, scope, no_return, in_loop);
+            var inlined = self.right.try_inline(compressor, scope, no_return, in_loop, in_await);
             if (!inlined) return;
             return make_node(AST_If, self, {
                 condition: make_condition(self.left),
@@ -13554,7 +13532,7 @@ Compressor.prototype.compress = function(node) {
             body[last] = inlined;
             return this;
         });
-        def(AST_Call, function(compressor, scope, no_return, in_loop) {
+        def(AST_Call, function(compressor, scope, no_return, in_loop, in_await) {
             if (compressor.option("inline") < 4) return;
             var call = this;
             if (call.is_expr_pure(compressor)) return;
@@ -13587,7 +13565,7 @@ Compressor.prototype.compress = function(node) {
                 }
                 defined.set("arguments", true);
             }
-            var async = is_async(fn);
+            var async = !in_await && is_async(fn);
             if (async) {
                 if (!compressor.option("awaits")) return;
                 if (!is_async(scope)) return;
@@ -13629,15 +13607,40 @@ Compressor.prototype.compress = function(node) {
                 if (has_arg_refs(fn, fn.rest)) return;
                 simple_argnames = false;
             }
-            if (no_return && !all(fn.body, function(stat) {
-                var abort = false;
-                stat.walk(new TreeWalker(function(node) {
-                    if (abort) return true;
-                    if (async && node instanceof AST_Await || node instanceof AST_Return) return abort = true;
-                    if (node instanceof AST_Scope && node !== fn) return true;
-                }));
-                return !abort;
-            })) return;
+            var verify_body;
+            if (no_return) {
+                verify_body = function(stat) {
+                    var abort = false;
+                    stat.walk(new TreeWalker(function(node) {
+                        if (abort) return true;
+                        if (async && node instanceof AST_Await || node instanceof AST_Return) return abort = true;
+                        if (node instanceof AST_Scope) return true;
+                    }));
+                    return !abort;
+                };
+            } else if (in_await && !is_async(fn)) {
+                verify_body = function(stat) {
+                    var abort = false;
+                    var find_return = new TreeWalker(function(node) {
+                        if (abort) return true;
+                        if (node instanceof AST_Return) return abort = true;
+                        if (node instanceof AST_Scope) return true;
+                    });
+                    stat.walk(new TreeWalker(function(node) {
+                        if (abort) return true;
+                        if (node instanceof AST_Try) {
+                            if (node.bfinally && all(node.body, function(stat) {
+                                stat.walk(find_return);
+                                return !abort;
+                            }) && node.bcatch) node.bcatch.walk(find_return);
+                            return true;
+                        }
+                        if (node instanceof AST_Scope) return true;
+                    }));
+                    return !abort;
+                };
+            }
+            if (verify_body && !all(fn.body, verify_body)) return;
             if (!safe_from_await_yield(fn, avoid_await_yield(compressor, scope))) return;
             fn.functions.each(function(def, name) {
                 scope.functions.set(name, def);
@@ -13737,10 +13740,10 @@ Compressor.prototype.compress = function(node) {
                 syms.add(def.id, sym);
             }
         });
-        def(AST_Conditional, function(compressor, scope, no_return, in_loop) {
+        def(AST_Conditional, function(compressor, scope, no_return, in_loop, in_await) {
             var self = this;
-            var body = self.consequent.try_inline(compressor, scope, no_return, in_loop);
-            var alt = self.alternative.try_inline(compressor, scope, no_return, in_loop);
+            var body = self.consequent.try_inline(compressor, scope, no_return, in_loop, in_await);
+            var alt = self.alternative.try_inline(compressor, scope, no_return, in_loop, in_await);
             if (!body && !alt) return;
             return make_node(AST_If, self, {
                 condition: self.condition,
@@ -13775,7 +13778,7 @@ Compressor.prototype.compress = function(node) {
             if (body) this.body = body;
             var obj = this.object;
             if (obj instanceof AST_Sequence) {
-                var inlined = inline_sequence(compressor, scope, true, in_loop, obj, 1);
+                var inlined = inline_sequence(compressor, scope, true, in_loop, false, obj, 1);
                 if (inlined) {
                     this.object = obj.tail_node();
                     inlined.body.push(this);
@@ -13794,7 +13797,7 @@ Compressor.prototype.compress = function(node) {
             }
             var cond = this.condition;
             if (cond instanceof AST_Sequence) {
-                var inlined = inline_sequence(compressor, scope, true, in_loop, cond, 1);
+                var inlined = inline_sequence(compressor, scope, true, in_loop, false, cond, 1);
                 if (inlined) {
                     this.condition = cond.tail_node();
                     inlined.body.push(this);
@@ -13826,10 +13829,10 @@ Compressor.prototype.compress = function(node) {
             var value = this.value;
             return value && value.try_inline(compressor, scope, undefined, in_loop === "try");
         });
-        function inline_sequence(compressor, scope, no_return, in_loop, node, skip) {
+        function inline_sequence(compressor, scope, no_return, in_loop, in_await, node, skip) {
             var body = [], exprs = node.expressions, no_ret = no_return;
-            for (var i = exprs.length - (skip || 0), j = i; --i >= 0; no_ret = true) {
-                var inlined = exprs[i].try_inline(compressor, scope, no_ret, in_loop);
+            for (var i = exprs.length - (skip || 0), j = i; --i >= 0; no_ret = true, in_await = false) {
+                var inlined = exprs[i].try_inline(compressor, scope, no_ret, in_loop, in_await);
                 if (!inlined) continue;
                 flush();
                 body.push(inlined);
@@ -13848,8 +13851,8 @@ Compressor.prototype.compress = function(node) {
                 j = i;
             }
         }
-        def(AST_Sequence, function(compressor, scope, no_return, in_loop) {
-            return inline_sequence(compressor, scope, no_return, in_loop, this);
+        def(AST_Sequence, function(compressor, scope, no_return, in_loop, in_await) {
+            return inline_sequence(compressor, scope, no_return, in_loop, in_await, this);
         });
         def(AST_SimpleStatement, function(compressor, scope, no_return, in_loop) {
             var body = this.body;
@@ -13865,12 +13868,12 @@ Compressor.prototype.compress = function(node) {
             });
             return body.try_inline(compressor, scope, no_return || false, in_loop);
         });
-        def(AST_UnaryPrefix, function(compressor, scope, no_return, in_loop) {
+        def(AST_UnaryPrefix, function(compressor, scope, no_return, in_loop, in_await) {
             var self = this;
             var op = self.operator;
             if (unary_side_effects[op]) return;
             if (!no_return && op == "void") no_return = false;
-            var inlined = self.expression.try_inline(compressor, scope, no_return, in_loop);
+            var inlined = self.expression.try_inline(compressor, scope, no_return, in_loop, in_await);
             if (!inlined) return;
             if (!no_return) scan_local_returns(inlined, function(node) {
                 node.in_bool = false;
@@ -13888,7 +13891,7 @@ Compressor.prototype.compress = function(node) {
             if (body) this.body = body;
             var exp = this.expression;
             if (exp instanceof AST_Sequence) {
-                var inlined = inline_sequence(compressor, scope, true, in_loop, exp, 1);
+                var inlined = inline_sequence(compressor, scope, true, in_loop, false, exp, 1);
                 if (inlined) {
                     this.expression = exp.tail_node();
                     inlined.body.push(this);


Test patch:
diff --git a/test/compress/awaits.js b/test/compress/awaits.js
index 1e1b482b8d1..04016c9cf15 100644
--- a/test/compress/awaits.js
+++ b/test/compress/awaits.js
@@ -3023,3 +3023,157 @@ issue_5506: {
     expect_stdout: "PASS"
     node_version: ">=8"
 }
+
+issue_5528_1: {
+    options = {
+        inline: true,
+    }
+    input: {
+        (async function() {
+            await function() {
+                try {
+                    return;
+                } finally {
+                    console.log("foo");
+                }
+            }();
+        })();
+        console.log("bar");
+    }
+    expect: {
+        (async function() {
+            await function() {
+                try {
+                    return;
+                } finally {
+                    console.log("foo");
+                }
+            }();
+        })();
+        console.log("bar");
+    }
+    expect_stdout: [
+        "foo",
+        "bar",
+    ]
+    node_version: ">=8"
+}
+
+issue_5528_2: {
+    options = {
+        inline: true,
+    }
+    input: {
+        (async function() {
+            await function() {
+                try {
+                    return 42;
+                } finally {
+                    console.log("foo");
+                }
+            }();
+        })();
+        console.log("bar");
+    }
+    expect: {
+        (async function() {
+            await function() {
+                try {
+                    return 42;
+                } finally {
+                    console.log("foo");
+                }
+            }();
+        })();
+        console.log("bar");
+    }
+    expect_stdout: [
+        "foo",
+        "bar",
+    ]
+    node_version: ">=8"
+}
+
+issue_5528_3: {
+    options = {
+        inline: true,
+    }
+    input: {
+        (async function() {
+            await function() {
+                try {
+                    FAIL;
+                } catch (e) {
+                    return console.log("foo");
+                } finally {
+                    console.log("bar");
+                }
+            }();
+        })();
+        console.log("baz");
+    }
+    expect: {
+        (async function() {
+            await function() {
+                try {
+                    FAIL;
+                } catch (e) {
+                    return console.log("foo");
+                } finally {
+                    console.log("bar");
+                }
+            }();
+        })();
+        console.log("baz");
+    }
+    expect_stdout: [
+        "foo",
+        "bar",
+        "baz",
+    ]
+    node_version: ">=8"
+}
+
+issue_5528_4: {
+    options = {
+        inline: true,
+    }
+    input: {
+        (async function() {
+            await function() {
+                try {
+                    return {
+                        then() {
+                            console.log("foo");
+                        },
+                    };
+                } finally {
+                    console.log("bar");
+                }
+            }();
+        })();
+        console.log("baz");
+    }
+    expect: {
+        (async function() {
+            await function() {
+                try {
+                    return {
+                        then() {
+                            console.log("foo");
+                        },
+                    };
+                } finally {
+                    console.log("bar");
+                }
+            }();
+        })();
+        console.log("baz");
+    }
+    expect_stdout: [
+        "bar",
+        "baz",
+        "foo",
+    ]
+    node_version: ">=8"
+}
diff --git a/test/reduce.js b/test/reduce.js
index ed76beed3fe..ffb1c2f5ee6 100644
--- a/test/reduce.js
+++ b/test/reduce.js
@@ -778,9 +778,9 @@ function compare_run_code(code, minify_options, result_cache, max_timeout) {
     function run(code, timeout) {
         if (minify_options.module) code = [
             '"use strict";',
-            "(async function(){",
+            "(async ()=>{",
             code,
-            "})();"
+            "})().catch(e=>console.log(e));",
         ].join("\n");
         return run_code(code, toplevel, result_cache, timeout);
     }
diff --git a/test/ufuzz/index.js b/test/ufuzz/index.js
index c8ee64dc69c..415ed45ba2e 100644
--- a/test/ufuzz/index.js
+++ b/test/ufuzz/index.js
@@ -2104,9 +2104,9 @@ if (require.main !== module) {
 function run_code(code, toplevel, timeout) {
     if (async && has_await) code = [
         '"use strict";',
-        "(async function(){",
+        "(async ()=>{",
         code,
-        "})();"
+        "})().catch(e=>console.log(e));",
     ].join("\n");
     return sandbox.run_code(sandbox.patch_module_statements(code), toplevel, timeout);
 }