Uglifyjs: ufuzz - bug with `unused`

Created on 8 Jan 2018  路  14Comments  路  Source: mishoo/UglifyJS

May be I need more :coffee: and/or :zzz:

// original code
// (beautified)
var _calls_ = 10, a = 100, b = 10, c = 0;

L59328: for (var brake1 = 5; a++ + [ , 0 ][1] && brake1 > 0; --brake1) {
    try {
        (c = c + 1) + ((c = c + 1) + {}[--b + []] || a || 3).toString();
    } finally {
        if (a++ + [ , 0 ][1]) {
            var b_2 = 5;
        } else {
            for (var brake6 = 5; --b + [ b_2 && b_2[[ delete b, a++ + 38..toString() ][{
                "": (c = 1 + c, (({} ^ undefined) & 23..toString() % -1) + (([ , 0 ].length === 2) - "number" && "b" - -2)),
                3: (c = 1 + c, (-3 + undefined ^ (2 ^ 0)) % (5 === 24..toString() && -4 <= "undefined")),
                foo: (c = 1 + c, ("function" != -2 && "a" % false) + (-4 + 5 != "number" >>> "object"))
            }.var]], --b + [ delete void (3 << "a" ^ "c" - 22), a++ + typeof (--b + b--), /[abc4]/.test((a++ + typeof (c = 1 + c,
            22 * -3 & 24..toString() >= "number" & delete (c = c + 1, -1)) || b || 5).toString()), {
                var: (c = 1 + c, (undefined / "function" || "c" + "b") >>> (void -4 >= "b" << NaN))
            }.Infinity ].Infinity, typeof f1 == "function" && --_calls_ >= 0 && f1() ] && brake6 > 0; --brake6) {
                var brake7 = 5;
                while (b_2 && b_2.var && --brake7 > 0) {
                    var bar = b_2 && b_2.b, b = a++ + ((b = a) ? --b + (0 === 1 ? a : b) : --b + {
                        3: a++ + ((c = 1 + c, (([ , 0 ].length === 2 && 3) === (24..toString() ^ undefined)) >> (2 === /[a2][^e]+$/) * (NaN / -5)) || a || 3).toString(),
                        b: --b + (typeof b_2 == "function" && --_calls_ >= 0 && b_2((c = 1 + c, b_2 && (b_2[typeof f1 == "function" && --_calls_ >= 0 && f1((c = 1 + c,
                        NaN === [ , 0 ][1] !== -3 + null ^ (-2 ^ {}) >> (1 != -3)), (c = 1 + c, "foo" % -4 == (null === true) ^ ("object" >> 2 || 22 >> true)), (c = 1 + c,
                        +(c = c + 1, void "bar")))] += ("number" <= false) % (b_2 && (b_2.null &= -2 | -0)) | (0 / 23..toString() || 5 - [ , 0 ][1]))), (c = 1 + c,
                        (b_2 /= -2 <= -4) + (Infinity >= this) + (([ , 0 ].length === 2 == /[a2][^e]+$/) > (Infinity ^ [ , 0 ].length === 2))))),
                        b: a++ + (typeof f1 == "function" && --_calls_ >= 0 && f1((c = 1 + c, ("object" >> /[a2][^e]+$/,
                        -3 == -5) && 25 - [ , 0 ][1] >>> (b_2 && (b_2[(c = 1 + c, -2 >> true << (null & /[a2][^e]+$/) << ((b_2 && (b_2[(c = 1 + c,
                        void ((true <= 25) >> ("foo" >>> /[a2][^e]+$/)))] = "bar" || /[a2][^e]+$/)) ^ (b_2 && (b_2[(c = 1 + c,
                        ((b_2 += 22 === 25) === "b" * [ , 0 ][1]) - ((-3 || 0) & 4 < "object"))] += 22 + "a"))))] += "undefined" + 24..toString())))))
                    }[!((b_2 && (b_2[a++ + (b_2 && b_2.Infinity)] = (NaN, 38..toString()) && ("" && 5))) ^ ((b_2 && (b_2.a = NaN && 22)) ^ (false && -3)))]);
                }
            }
        }
        {
            var brake9 = 5;
            while (--b + /[abc4]/.test(((c = c + 1) + b++ || b || 5).toString()) && --brake9 > 0) {
                var brake10 = 5;
                L59329: do {
                    if ((a++ + b_2 || 7).toString()[b_2 && b_2.var]) {
                        var brake12 = 5;
                        while ((c = c + 1) + ((("a" || "foo") < (22, "undefined")) << (4 | -3) - ("" & -1)) && --brake12 > 0) {
                            ({});
                        }
                    }
                } while (--b + (b_2 = a++ + void function() {
                    {
                        return a++ + (--b + (b &= a) ? --b + (b = a) : (c = 1 + c, c = c + 1, b_2 %= (-1 ^ 38..toString()) & ([] || -3)) ? (c = 1 + c,
                        void false - (false & -0) !== ("function" / 23..toString() ^ "" === 23..toString())) : (c = 1 + c,
                        b_2 && (b_2.foo += ("c" > -1) / (2 > 1) || (true == 3) - ("bar" & Infinity))));
                    }
                    {
                        var undefined = function bar_2() {
                            c = 1 + c, (false != 1) < ("undefined" ^ "a") | (c = c + 1, 5 % {});
                            c = 1 + c, 38..toString() % NaN != (1 === 0), NaN % 23..toString(), -5 !== 23..toString();
                        }((c = 1 + c, (b_2 && (b_2.c += 2 === -2 && "foo" == "object")) ^ (/[a2][^e]+$/ === 5 || this && NaN)), NaN, "c");
                    }
                    for (var brake18 = 5; --b && brake18 > 0; --brake18) {
                        L59330: for (var brake19 = 5; void (([ , 0 ][1] << "object") + (c = c + 1, [ , 0 ].length === 2) + ((25 | [ , 0 ][1]) !== 25 >= /[a2][^e]+$/)) && brake19 > 0; --brake19) {
                            var brake20 = 5;
                            L59331: while ((c = 1 + c, ("b" < -0 | 1 * 1) > (c = c + 1, [ , 0 ][1] < 23..toString())) && --brake20 > 0) {
                                c = 1 + c, b_2 = ("number" >> "function") % (38..toString() ^ -5) > ("bar" ^ 1 && (c = c + 1,
                                Infinity));
                            }
                        }
                    }
                    try {
                        {
                            var brake23 = 5;
                            do {
                                {
                                }
                            } while ((c = c + 1) + (b = a) && --brake23 > 0);
                        }
                    } catch (b) {
                        var b;
                        switch (c = 1 + c, (-0 | false) >>> 23..toString() * -2 | ("object", 24..toString()) == ("foo" || false)) {
                          case c = 1 + c, Infinity < {} >= (c = c + 1, -1) && "" * "b" | -2 & {}:
                            ;
                            break;

                          case c = 1 + c, (5 | 24..toString()) - (/[a2][^e]+$/ | null) ^ (-4 !== 38..toString() && [ , 0 ].length === 2 == "undefined"):
                            ;
                            break;

                          default:
                            ;

                          case c = 1 + c, (-2 || 3) - (5 === "foo") & (("" || [ , 0 ][1]) & [ , 0 ][1] % 25):
                            ;
                        }
                    }
                }()) && --brake10 > 0);
            }
        }
    }
}

var b_1;

console.log(null, a, b, c, Infinity, NaN, undefined);

```js
// uglified code
// (beautified)
for (var t, o = 10, n = 100, i = 10, r = 0, f = 5; 0 + n++ && f > 0; --f) {
try {
r += 1, ((r += 1) + {}[--i + []] || n || 3).toString();
} finally {
if (0 + n++) {
var e = 5;
} else {
for (var a = 5; --i + [ e && e[[ delete i, n++ + 38..toString() ][{
"": (r = 1 + r, ((void 0 ^ {}) & 23..toString() % -1) + ((2 === [ , 0 ].length) - "number" && NaN)),
3: (r = 1 + r, 2 % (5 === 24..toString() && !1)),
foo: (r = 1 + r, NaN)
}.var]], --i + [ !0, n++ + typeof (--i + i--), /[abc4]/.test((n++ + (r = 1 + r,
typeof (-66 & 24..toString() >= "number" & (r += 1, !0))) || i || 5).toString()), {
var: (r = 1 + r, 0)
}.Infinity ].Infinity, "function" == typeof f1 && --o >= 0 && f1() ] && a > 0; --a) {
for (var g = 5; e && e.var && --g > 0; ) {
e && e.b, i = n++ + ((i = n) ? --i + i : --i + {
3: n++ + (r = 1 + r, ((2 === [ , 0 ].length && 3) === (void 0 ^ 24..toString())) >> NaN || n || 3).toString(),
b: --i + ("function" == typeof e && --o >= 0 && e((r = 1 + r, e && (e["function" == typeof f1 && --o >= 0 && f1((r = 1 + r,
!0 ^ (-2 ^ {}) >> !0), (r = 1 + r, 11), (r = 1 + r, +void (r += 1)))] += !1 % (e && (e.null &= -2)) | (0 / 23..toString() || 5))), (r = 1 + r,
(e /= !1) + (1 / 0 >= this) + ((2 === [ , 0 ].length == /[a2][^e]+$/) > (1 / 0 ^ 2 === [ , 0 ].length))))),
b: n++ + ("function" == typeof f1 && --o >= 0 && f1((r = 1 + r, !1)))
}[!(!1 ^ (e && (e[n++ + (e && e.Infinity)] = 38..toString() && "")) ^ (e && (e.a = NaN)))]);
}
}
}
for (var v = 5; --i + /[abc4]/.test(((r += 1) + i++ || i || 5).toString()) && --v > 0; ) {
var l = 5;
do {
if ((n++ + e || 7).toString()[e && e.var]) {
for (var S = 5; (r += 1) + (!0 << -3) && --S > 0; ) {}
}
} while (--i + (e = n++ + (void 0, void (n++, --t + (t &= n) ? (--t, t = n) : (r = 1 + r,
r += 1, (e %= (-1 ^ 38..toString()) & ([] || -3)) ? (r = 1 + r, 23..toString(),
23..toString()) : (r = 1 + r, e && (e.foo += 0)))))) && --l > 0);
}
}
}

console.log(null, n, i, r, 1 / 0, NaN, void 0);

```js
original result:
null 171 -15 95 Infinity NaN undefined

uglified result:
null 168 -14 37 Infinity NaN undefined

minify(options):
{
  "toplevel": true
}

Suspicious compress options:
  dead_code
  inline
  unused
bug

All 14 comments

Can't repro with any version of node, however jxcore with mozilla VM yields a different result once uglified:

$ cat t2749.js | jx
null 171 170 35 Infinity NaN undefined
$ cat t2749.js | bin/uglifyjs -c | jx
null 171 -15 95 Infinity NaN undefined

Try running it in FireFox.

I'm beginning to think I need a way to reset SymbolDef.next_id between runs, because rename will generate variable names of different lengths, and that may affect your smart AST_SymbolRef replacement logic.

I see.

Bingo:

--- a/lib/compress.js
+++ b/lib/compress.js
@@ -5007,7 +5007,7 @@ merge(Compressor.prototype, {
                     if (compressor.option("unused") && !compressor.exposed(d)) {
                         overhead = (name_length + 2 + value_length) / d.references.length;
                     }
-                    d.should_replace = value_length <= name_length + overhead ? fn : false;
+                    d.should_replace = fn;
                 } else {
                     d.should_replace = false;
                 }
$ cat test.js | node
null 171 -15 95 Infinity NaN undefined

$ uglifyjs test.js --toplevel -mc | node
null 168 -14 37 Infinity NaN undefined

Which JS engine is correct?

$ cat bug.js
var b = 10;
(function() {
    return b &= 7;
    try {} catch (b) {
        var b;
    }
}());
console.log(b);
$ cat bug.js | node
10



md5-ad542f584b8cc56773abf448ff9df849



$ cat bug.js | jx
2



md5-ad542f584b8cc56773abf448ff9df849



$ cat bug.js | bin/uglifyjs -bc | jx
10



md5-ad542f584b8cc56773abf448ff9df849



$ cat bug.js | bin/uglifyjs -bc | node
10

Please don't tell me I need --firefox for some catch corner case... :see_no_evil:

FF57 outputs 10.

Can you try on a couple of non-V8 engines?

-                    d.should_replace = value_length <= name_length + overhead ? fn : false;
+                    d.should_replace = fn;

Just verifying... the bug is unrelated to this logic - correct?

The original test case seems to fail for something else, but no it's not the culprit for the bug:

var a = 100, b = 10, c = 0;

while (--b) {
    (function() {
        return --b + (b &= a) ? b = a : c++;
        try {
        } catch (b) {
            var b;
        }
    })();
}

console.log(null, a, b, c, Infinity, NaN, undefined);
$ cat test.js | node
null 100 0 9 Infinity NaN undefined

$ uglifyjs test.js -bmc toplevel | node
null 100 0 1 Infinity NaN undefined

$ uglifyjs test.js -bmc toplevel
for (var a6, b = 10, c = 0; --b; ) void 0, --a6 + (a6 &= 100) ? a6 = 100 : c++;

console.log(null, 100, b, c, 1 / 0, NaN, void 0);

As for your test case above:

  • IE8 & IE11 give 10
  • Firefox 43 gives 2
  • Safari 5 gives 10
  • Opera 12 gives 10
var a = 100, b = 10, c = 0;
while (--b) {
    (function() {
        return --b + (b &= a) ? b = a : c++;
        try {
        } catch (b) {
            var b;
        }
    })();
}
console.log(a, b, c);

```sh
$ cat test.js | node
100 0 9

$ uglifyjs test.js --toplevel -bmc | node
100 0 1

$ uglifyjs test.js --toplevel -bmc unused=keep_assigned | node
100 0 9

```sh
$ uglifyjs test.js --toplevel -bmc
for (var o, l = 10, r = 0; --l; ) void 0, --o + (o &= 100) ? o = 100 : r++;

console.log(100, l, r);

$ uglifyjs test.js --toplevel -bmc unused=keep_assigned
for (var o, l = 10, r = 0; --l; ) o = void 0, --o + (o &= 100) ? o = 100 : r++;

console.log(100, l, r);

@kzc would you mind handling the Firefox peculiarity while I focus on this unused bug?

Nothing to handle for FireFox - the old version of their engine appears to be wrong, and their most recent version matches the other engines.

Although it's curious how the new test case test.js is very similar to bug.js.

The new test case above (test.js) produces either an error on jxcore:

$ jx test.js 
ReferenceError: b is not defined

or infinite loop:

cat test.js | jx

(does not return)

Wow, that's _very_ wrong... like, IE8 global-scope AST_SymbolCatch wrong... :hurtrealbad:

FF57 produces 100 0 9 for test.js - matching node.

Was this page helpful?
0 / 5 - 0 ratings