Repository: mishoo/UglifyJS (JavaScript)

Issue:
ufuzz failure
```js
// original code
// (beautified)
var _calls_ = 10, a = 100, b = 10, c = 0;

{
    var brake1 = 5;
    L18825: do {
    } while (a++ + (a = a++) && --brake1 > 0);
}

{
    var brake3 = 5;
    do {
        for (var brake4 = 5; --b + /[abc4]/.test((a++ + [] || b || 5).toString()) && brake4 > 0; --brake4) {
            L18826: {
                {
                    var brake6 = 5;
                    do {
                        {
                            var expr7 = b = a;
                            for (var [ key7 ] in expr7) {
                                c = 1 + c;
                                const arguments_2 = expr7[key7];
                                ({
                                    ...(c = c + 1) + arguments_2,
                                    0: ({} <= "c" == (c = c + 1, "function")) <= (22 ^ [ , 0 ][1]) >> 24..toString() % undefined,
                                    "": key7 = (c = c + 1) + [],
                                    ...key7
                                });
                            }
                        }
                    } while ((([ ...[ [ arguments_1 ] ] ], NaN_1, a_2) => (c = c + 1) + (arguments_1 && arguments_1[--b + (a++ + (NaN_1 && typeof NaN_1.b == "function" && --_calls_ >= 0 && NaN_1.b("undefined", this, -4)) || 9).toString()[--b + (a_2 && a_2.next)]]))([ [], "undefined" ], 5) && --brake6 > 0);
                }
                for (var brake9 = 5; a++ + {}.async && brake9 > 0; --brake9) {
                    var brake10 = 5;
                    while (("number" ^ true) * (5 + "object") ^ 23..toString() / false >>> ("undefined" >> "undefined") && --brake10 > 0) {
                        try {
                        } catch (yield_2) {
                            c = c + 1;
                            for (var brake14 = 5; {
                                NaN: (c = 1 + c, ((24..toString() && -5) ^ [ , 0 ][1] > true) < ("number" === "undefined") % (yield_2 && (yield_2.null <<= {} == {}))),
                                foo: (c = 1 + c, 38..toString() >> ([ , 0 ].length === 2) >> ~"object" != ("b" !== -4 !== (yield_2 && (yield_2.in = [ , 0 ][1] && 25)))),
                                get c() {
                                    c = 1 + c, (yield_2 && ([ yield_2[c = 1 + c, (yield_2 && (yield_2.get = (c = c + 1, 
                                    null) | "b" / 0)) ^ ("bar" ^ false) << (5 >> "function")] ] = [ (1 ^ 2) != true * -2 ])) ^ (-1 || 3) <= (Infinity >= {});
                                },
                                null: (c = 1 + c, ((c = c + 1, 1) != (yield_2 && (yield_2[c = 1 + c, 25 & Infinity || [ , 0 ].length === 2 ^ "function", 
                                23..toString() / true << ("foo" >>> [])] += -4 != -2))) * ((false, 0) & (null && NaN))),
                                length: (c = 1 + c, ([] && "") <= (yield_2 && (yield_2[c = 1 + c, ("object" << "a") % (25 << []) * ((22 ^ "") >>> ("bar" >> -3))] += 5 % "a")), 
                                ("undefined" === -0) > (yield_2 = "foo" >>> [ , 0 ][1]))
                            }[(c = c + 1) + /[abc4]/.test(((c = 1 + c, delete (false >>> "foo") == (-0 || -4) % (true >= 24..toString())) || b || 5).toString())] && brake14 > 0; --brake14) {
                                try {
                                    c = c + 1;
                                } finally {
                                    c = 1 + c, ([ , 0 ].length === 2 ^ 24..toString() || 23..toString() & -1) <= (25 <= -4 === (-4 ^ 25));
                                    c = 1 + c, c = c + 1, (22 || 25) >>> (yield_2 && (yield_2.NaN = -4 === 0));
                                }
                            }
                        }
                    }
                }
            }
        }
    } while (/[abc4]/g.exec((a++ + a++ || b || 5).toString()) && --brake3 > 0);
}

console.log(null, a, b, c, Infinity, NaN, undefined);
```
```js
// uglified code
// (beautified)
for (var o, i, n, r, t, a = 100, e = 10, l = 0, c = 5; a++ + (a = a++) && 0 < --c; ) {}

o = 5;

do {
    for (i = 5; --e + /[abc4]/.test((a++ + [] || e || 5).toString()) && 0 < i; --i) {
        n = 5;
        do {
            for (var [ f ] in r = e = a) {
                l = 1 + l, r[f], l += 1, l += 1, {
                    ...f = (l += 1) + []
                };
            }
        } while ((l += 1) + "u"[--e + ("" + (a++ + !1 || 9))[--e + [][0]]] && 0 < --n);
        for (t = 5; a++ + {}.async && 0 < t; --t) {}
    }
} while (/[abc4]/g.exec((a++ + a++ || e || 5).toString()) && 0 < --o);

console.log(null, a, e, l, Infinity, NaN, void 0);
```
```
original result:
null 118 113 5 Infinity NaN undefined

uglified result:
null 141 133 10 Infinity NaN undefined
```
```js
// reduced test case (output will differ)

// (beautified)
(([ ...[ arguments_1 ] ]) => console.log(arguments_1))([ [], "undefined" ]);
// output: {}
// 
// minify: undefined
// 
// options: {
//   "compress": {
//     "hoist_vars": true,
//     "keep_infinity": true,
//     "passes": 1000000,
//     "unsafe": true
//   },
//   "keep_fnames": true,
//   "toplevel": true,
//   "mangle": {
//     "v8": true
//   },
//   "output": {
//     "v8": true
//   },
//   "validate": true
// }
```
```
minify(options):
{
  "compress": {
    "hoist_vars": true,
    "keep_infinity": true,
    "passes": 1000000,
    "unsafe": true
  },
  "keep_fnames": true,
  "toplevel": true,
  "mangle": {
    "v8": true
  },
  "output": {
    "v8": true
  }
}

Suspicious compress options:
  evaluate
  reduce_vars
  rests
  unsafe
  unused
```


Discussion:
off topic...

I noticed a few ufuzz reports that were variations of this theme - static initializers referencing `this` within a class declaration:

```
$ cat cef2.js 
class C extends function() {} {
    static b = new function f(x) {
        x && console.log(this === void 0 ? "PASS" : "FAIL");
        if (void 0 === x) f(1);
    }();
}
```
expected:
```
$ cat cef2.js | node
PASS
```
actual:
```
$ cat cef2.js | uglify-js -bc toplevel
new function f(x) {
    x && console.log(void 0 === this ? "PASS" : "FAIL"), void 0 === x && f(1);
}();
```
```
$ cat cef2.js | uglify-js -bc toplevel | node
FAIL
```

another variation:
```
$ cat cef4.js 
class C extends function() {} {
    [new function f(x) {
        x && console.log(this === void 0 ? "PASS" : "FAIL");
        if (void 0 === x) f(1);
    }()]() {}
}

$ cat cef4.js | node
PASS

$ cat cef4.js | uglify-js -bc toplevel | node
FAIL
```

But both will pass if the NodeJS `--input-type` is "module" (`.mjs` suffix) as opposed to the default "commonjs" type (`.js` suffix):

```
$ cat cef2.js | node --input-type module
PASS

$ cat cef2.js | uglify-js -bc toplevel | node --input-type module
PASS
```
```
$ cat cef4.js | node --input-type module
PASS

$ cat cef4.js | uglify-js -bc toplevel | node --input-type module
PASS
```
`this` within `class` seems to have become the predominant type of false positives of late...

As to your examples, if you feed them through `ufuzz`:
https://github.com/mishoo/UglifyJS/blob/c09f63aefb4609bbdae259213e7fa458d959e20b/test/ufuzz/index.js#L2456

you'll notice that both will pass just fine, which is due to:
https://github.com/mishoo/UglifyJS/blob/c09f63aefb4609bbdae259213e7fa458d959e20b/test/ufuzz/index.js#L2515-L2524

(I guess `--input-type module` does something similar.)

These patch routines (are designed to) fail when there are multiple types of false postiives within a single test case.
I guess it's harmless since such output wouldn't typically run as a non-strict script.

But if you wanted to cover the non-strict script scenario, you could emit a `"use strict"` at the top of the output:

```
$ cat cef2.js | node
PASS

$ cat cef2.js | uglify-js -c toplevel | node
FAIL

$ cat cef2.js | (echo '"use strict";' ; uglify-js -c toplevel) | node
PASS
```

to match the strict semantics within the outer `class` that was dropped.



Patch:
diff --git a/lib/compress.js b/lib/compress.js
index c19091d7d1f..3820a3adb29 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -12313,8 +12313,10 @@ merge(Compressor.prototype, {
 
     OPT(AST_DestructuredArray, function(self, compressor) {
         if (compressor.option("rests") && self.rest instanceof AST_DestructuredArray) {
-            self.elements = self.elements.concat(self.rest.elements);
-            self.rest = self.rest.rest;
+            return make_node(AST_DestructuredArray, self, {
+                elements: self.elements.concat(self.rest.elements),
+                rest: self.rest.rest,
+            });
         }
         return self;
     });


Test patch:
diff --git a/test/compress/rests.js b/test/compress/rests.js
index 45d8e222b53..a62e380a9d9 100644
--- a/test/compress/rests.js
+++ b/test/compress/rests.js
@@ -1069,3 +1069,25 @@ issue_5100_2: {
     expect_stdout: "PASS"
     node_version: ">=10"
 }
+
+issue_5108: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        rests: true,
+        unsafe: true,
+        unused: true,
+    }
+    input: {
+        console.log(function([ ...[ a ] ]) {
+            return a;
+        }([ "PASS", "FAIL" ]));
+    }
+    expect: {
+        console.log(function([]) {
+            return "PASS";
+        }([ "PASS", "FAIL" ]));
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}