Uglifyjs: Fuzz Bug - `pure_getter`, `unused`, `reduce_vars`, `for( in )`

Created on 23 Apr 2017  路  13Comments  路  Source: mishoo/UglifyJS

This one is pretty specific in that it requires bin/uglifyjs -c passes=2,keep_fargs=0 to trigger.

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

c = c + 1;

for (var brake2 = 5; b + 1 - .1 - .1 - .1 && brake2 > 0; --brake2) {
    try {
        {
            var foo_2 = function f0(a_1) {
                NaN;
                {
                    var expr6 = --b + typeof (a++ + [ ,  ].undefined);
                    for (var key6 in expr6) {
                        var NaN_1 = a_1 && a_1.null, NaN_1 = [ b !== a, b == a, b = a ].null;
                    }
                }
            }((c = c + 1) + --b);
        }
    } catch (b_1) {
        {
            var expr8 = (c = c + 1) + void function a_2() {
                if (~a) {
                    {
                        return a++ + {
                            "-2": (c = 1 + c, (foo_2 === "bar" >>> "number") * (([ , 0 ].length === 2) * -3) ^ -4 + -1 >= ("undefined" || [ , 0 ][1]))
                        }[(c = 1 + c, ((b_1 && (b_1.null /= {} <= -3)) ^ (-5 ^ /[a2][^e]+$/)) & (undefined - 23..toString()) * (0 <= "foo"))];
                    }
                    try {
                        {
                            var expr13 = (c = 1 + c, a_2 && (a_2[23..toString()] <<= -1 < 3 <= (1 !== /[a2][^e]+$/) > (true & []) << true * 5));
                            L42319: for (var key13 in expr13) {
                                c = 1 + c;
                                var b_1 = expr13[key13];
                                c = 1 + c, ((23..toString() === -1) >= ("function", /[a2][^e]+$/)) >> ((b_1 && (b_1.var = undefined >>> "")) >= (NaN <= [ , 0 ][1]));
                            }
                        }
                    } finally {
                        c = 1 + c, (b_1 = (24..toString(), "") > (NaN & -3)) >> (38..toString() / "" !== ([ , 0 ].length === 2 != []));
                        c = 1 + c, (a_2 != (-3 <= "object") >>> ({} ^ "foo")) <= (void -3 == 0 * -0);
                    }
                } else {}
                (c = c + 1) + {
                    "-2": --b + [ (c = 1 + c, (null != -5) - (-4 >> 4) != (null >>> -3 || "" >> 2)), (c = 1 + c,
                    a_2 && (a_2[a++ + (a_2 && a_2.b)] >>= (true ^ NaN ^ "foo" !== -0) % (foo_2 && foo_2.foo != "function" / null < ("" && 38..toString())))), (c = 1 + c,
                    (bar_2 /= true ^ 23..toString()) ^ "object" == "foo" && (c = c + 1, "foo") === "undefined" <= /[a2][^e]+$/), (c = 1 + c,
                    (-"" === "bar" * "function") / ((25 || "object") | ~0)) ],
                    "": (c = c + 1) + /[abc4]/.test((-a || b || 5).toString()),
                    c: a++ + (++a ? foo_2 && foo_2.foo : b == a)
                }.length;
            }();
            for (var key8 in expr8) {
                c = 1 + c;
                var bar_2 = expr8[key8];
            }
        }
        switch (b + 1 - .1 - .1 - .1) {
          default:
            c = c + 1;

          case delete ((0 || 22 || "bar" + 25) / ((true | []) >>> (-2 >= {}))):
            break;

          case b == a:
            switch (a++ + new function() {}()) {
              case (c = c + 1) + (--b + ((-2 || undefined) !== (null && 0) || 38..toString() / -0 >> 1 / 23..toString())):
              case a++ + [ a++ + (b &= a), a++ + [ b_1 && b_1.foo ].length ][1 === 1 ? a : b]:
                break;

              case (c = c + 1) + {
                    0: bar_2 && bar_2.null,
                    "": --b + {}[b === a],
                    Infinity: --b + [ typeof (c = 1 + c, ("function" && -4) < ("bar" != "object") & (null !== "" ^ (b_1 && (b_1.in = "function" == -1)))), typeof (c = 1 + c,
                    (Infinity || 38..toString()) >>> ([ , 0 ][1] & Infinity) == (foo_2 && (foo_2.length += 3 >= 1)) + (Infinity > 4)), /[abc4]/.test(((c = 1 + c,
                    (Infinity | null) - (b_1 == 1 < -5) > (("bar" ^ -5) != (3, "function"))) || b || 5).toString()), (c = c + 1) + !function bar_2() {
                    }() ],
                    foo: (c = c + 1) + function foo() {
                        return c = 1 + c, -0 % 22 + 2 / -3 && "foo" + Infinity - (-4 + 24..toString());
                        {
                        }
                        {
                            var expr25 = (c = 1 + c, ((2, "") >= -1 % 0) >> (Infinity ^ -0 | ([ , 0 ].length === 2) % 25));
                            L42320: for (var key25 in expr25) {
                                c = 1 + c, 2 << "object" !== (-3 & "object") ^ (NaN, 25) <= "object" - "object";
                            }
                        }
                        {
                            var brake27 = 5;
                            while ((c = 1 + c, ((bar_2 >>= 24..toString() || 4) | 0 * "object") << ((b_1 && b_1.foo != "foo" >>> 23..toString()) <= ([ , 0 ][1] | "bar"))) && --brake27 > 0) {
                                c = 1 + c, (-3 << undefined < (38..toString(), 25)) >> (b_1 >>>= {} | {} && (c = c + 1,
                                23..toString()));
                            }
                        }
                    }
                }[/[a2][^e]+$/]:
                {
                    var brake29 = 5;
                    while (typeof undefined_2 === "unknown" && --brake29 > 0) {
                        switch (a++ + ((2 & 4 ^ /[a2][^e]+$/ & 0) == -2 << [] << (c = c + 1, 2))) {
                          case 1 === 1 ? a : b:
                            {
                            }
                            {
                            }
                            break;

                          default:
                            {
                            }

                          case +function parseInt_2() {
                                c = 1 + c, (2 == 2) + ("foo" <= NaN) << (bar_2 && (bar_2[(c = 1 + c, (1 % 5 || [] !== "") > ({} == -3) >> ({} << -4))] /= undefined > -2)) / ("object" - 4);
                            }():
                            for (var brake35 = 5; (c = 1 + c, ([ , 0 ][1] ^ "number") < 4 / false, ([ , 0 ][1] && "foo") | "object" + ([ , 0 ].length === 2)) && brake35 > 0; --brake35) {
                                c = 1 + c, c = c + 1, (1 ^ [ , 0 ][1]) >= (3 <= 38..toString());
                            }
                            break;

                          case typeof c_2 == "special":
                            switch (c = 1 + c, (24..toString() && 22) >= (false < 5) != ({} > "object" === ("undefined" && 24..toString()))) {
                              case c = 1 + c, (1 << "" && (b_1 >>>= "bar" % 1)) >= -4 - "undefined" + (true >>> true):
                                ;
                                break;

                              case c = 1 + c, foo_2 = (NaN >= 24..toString() > (38..toString() !== "function")) << (c = c + 1,
                                undefined) / (4 ^ 22):
                                ;

                              default:
                                ;

                              case c = 1 + c, (-4 >> "" >>> (undefined << [ , 0 ][1])) * ((b_1 && b_1.length === (2 & 24..toString())) & 1 < 1):
                                ;
                            }
                            break;
                        }
                    }
                }
                break;

              case a++ + -1:
                c = c + 1;
            }
            a++ + 5;
            break;

          case a++ + (0 === 1 ? a : b):
            break;
        }
    } finally {
        {
            var brake40 = 5;
            do {
                for (var brake41 = 5; --b + void ((("bar", [ , 0 ].length === 2) !== -2 << -0) * (bar_2 && (bar_2.a %= -3 & -5) || -23..toString())) && brake41 > 0; --brake41) {
                    var foo_1;
                }
            } while (a++ + /[abc4]/.test((a++ + {
                undefined: (c = c + 1) + (b = a),
                length: --b + !("object" + {} ^ /[a2][^e]+$/ == 23..toString(), c = c + 1, "" - ([ , 0 ].length === 2)),
                in: (c = c + 1) + (bar_2 && bar_2.null),
                a: b--
            } || b || 5).toString()) && --brake40 > 0);
        }
    }
}

console.log(null, a, b, c);

```js
// uglified code
// (beautified)
var a = 100, b = 10, c = 0;

c += 1;

for (var brake2 = 5; b + 1 - .1 - .1 - .1 && brake2 > 0; --brake2) {
try {
var foo_2 = function() {
var c = --b + typeof (a++ + [ , ].undefined);
for (var o in c) {
var r = a_1 && a_1.null, r = [ b !== a, b == a, b = a ].null;
}
}((c += 1, --b));
} catch (o) {
var expr8 = (c += 1) + void function o() {
if (~a) {
return a++ + {
"-2": (c = 1 + c, (0 === foo_2) * (-3 * (2 === [ , 0 ].length)) ^ !1)
}[(c = 1 + c, (-5 ^ (r && (r.null /= {} <= -3))) & !1 * (void 0 - 23..toString()))];
var r;
}
c += 1, --b, c = 1 + c, c = 1 + c, o && (o[a++ + (o && o.b)] >>= 0 % (foo_2 && 0 != foo_2.foo)),
c = 1 + c, !1 ^ (bar_2 /= !0 ^ 23..toString()) && (c += 1), c = 1 + c, c += 1, /[abc4]/.test((-a || b || 5).toString()),
a++, ++a && foo_2 && foo_2.foo;
}();
for (var key8 in expr8) {
c = 1 + c;
var bar_2 = expr8[key8];
}
switch (b + 1 - .1 - .1 - .1) {
default:
c += 1;

      case !0:
        break;

      case b == a:
        switch (a++ + new function() {}()) {
          case (c += 1) + (--b + !0):
          case a++ + [ a++ + (b &= a), a++ + [ o && o.foo ].length ][a]:
            break;

          case (c += 1) + {
                0: bar_2 && bar_2.null,
                "": --b + {}[b === a],
                Infinity: --b + [ (c = 1 + c, typeof (!0 & (!0 ^ (o && (o.in = !1))))), (c = 1 + c,
                typeof (1 / 0 >>> ([ , 0 ][1] & 1 / 0) == (foo_2 && (foo_2.length += !0)) + !0)), /[abc4]/.test((c = 1 + c,
                0 - (0 == o) > !0 || b || 5).toString()), (c += 1) + !0 ],
                foo: (c += 1) + function() {
                    return c = 1 + c, "foo" + 1 / 0 - (-4 + 24..toString());
                }
            }[/[a2][^e]+$/]:
            for (var brake29 = 5; "unknown" == typeof undefined_2 && --brake29 > 0; ) {
                switch (a++ + (0 == -2 << [] << (c += 1, 2))) {
                  case a:
                    break;

                  default:
                  case +function() {
                        c = 1 + c, bar_2 && (bar_2[(c = 1 + c, 1 > (-3 == {}) >> ({} << -4))] /= !1);
                    }():
                    for (var brake35 = 5; c = 1 + c, ([ , 0 ][1] && "foo") | "object" + (2 === [ , 0 ].length) && brake35 > 0; --brake35) {
                        c = 1 + c, c += 1, 38..toString();
                    }
                    break;

                  case "special" == typeof c_2:
                    switch (c = 1 + c, (24..toString() && 22) >= !0 != ({} > "object" === 24..toString())) {
                      case c = 1 + c, (o >>>= NaN) >= NaN:
                        break;

                      case c = 1 + c, foo_2 = (NaN >= 24..toString() > ("function" !== 38..toString())) << void (c += 1) / 18:
                      default:
                      case c = 1 + c, (-4 >>> (void 0 << [ , 0 ][1])) * (!1 & (o && o.length === (2 & 24..toString()))):
                    }
                }
            }
            break;

          case a++ - 1:
            c += 1;
        }
        a++;
        break;

      case a++ + b:
    }
} finally {
    var brake40 = 5;
    do {
        for (var brake41 = 5; --b + void (bar_2 && (bar_2.a %= -7) || 23..toString()) && brake41 > 0; --brake41) {
            var foo_1;
        }
    } while (a++ + /[abc4]/.test((a++ + {
        undefined: (c += 1) + (b = a),
        length: --b + (23..toString(), c += 1, !("" - (2 === [ , 0 ].length))),
        in: (c += 1) + (bar_2 && bar_2.null),
        a: b--
    } || b || 5).toString()) && --brake40 > 0);
}

}

console.log(null, a, b, c);

```js
original result:
null 155 153 81

uglified result:
null 165 163 101

minify(options):
{ compress: { keep_fargs: false, passes: 3 } }

Suspicious compress options:
  passes
  pure_getters
  reduce_vars
  unused
bug

All 13 comments

Perhaps you've already isolated it, but notice the removal of the function f0 argument a_1 even though it is still referenced:

original:

for (var brake2 = 5; b + 1 - .1 - .1 - .1 && brake2 > 0; --brake2) {
    try {
        {
            var foo_2 = function f0(a_1) {
                NaN;
                {
                    var expr6 = --b + typeof (a++ + [ ,  ].undefined);
                    for (var key6 in expr6) {
                        var NaN_1 = a_1 && a_1.null, NaN_1 = [ b !== a, b == a, b = a ].null;
                    }
                }
            }((c = c + 1) + --b);
        }
    } catch (b_1) {

bin/uglifyjs bug.js -c passes=2,keep_fargs=0 -b bracketize

for (var brake2 = 5; b + 1 - .1 - .1 - .1 && brake2 > 0; --brake2) {
    try {
        var foo_2 = function() {
            var expr6 = --b + typeof (a++ + [ ,  ].undefined);
            for (var key6 in expr6) {
                var NaN_1 = a_1 && a_1.null, NaN_1 = [ b !== a, b == a, b = a ].null;
            }
        }((c += 1, --b));
    } catch (b_1) {

@kzc thanks for looking into it :+1:

I am working on #1821 (again) so haven't started investigating this yet.

Reduced test case:

$ cat bug.js
var b = 10;

try {
    !function(arg) {
        for (var key in "hi") {
            var n = arg.baz, n = [ b = 42 ];
        }
    }(--b);
} catch (ex) {}

console.log(b);
$ node bug.js 
42



md5-5aa8b70ec666ca70883896e7506688f2



$ bin/uglifyjs bug.js -c passes=2,keep_fargs=0 -b bracketize | node
9



md5-5aa8b70ec666ca70883896e7506688f2



$ bin/uglifyjs bug.js -c passes=2,keep_fargs=0 -b bracketize
var b = 10;

try {
    !function() {
        for (var key in "hi") {
            var n = arg.baz, n = [ b = 42 ];
        }
    }(--b);
} catch (ex) {}

console.log(b);

It's a strange bug - clearly there's a reference to the argument arg, but it was dropped.

Disabling reduce_vars fixes it:

$ bin/uglifyjs bug.js -c passes=2,keep_fargs=0,reduce_vars=1 -b bracketize | node
9

$ bin/uglifyjs bug.js -c passes=2,keep_fargs=0,reduce_vars=0 -b bracketize | node
42

With your example passes=2 is not required to trigger this bug.

I know about keep_fargs, reduce_vars & unused being the culprit - searching for others to complete the picture.

The missing piece of the puzzle being pure_getters=strict - now onward to locate and fix the bug :wink:

The problem is that n is unused and should be eliminated, but somehow drop_unused() couldn't manage to get rid of it (or the whole var statement for that matter).

n is used twice within the same variable declaration. I had a similar problem with collapse_vars a year ago.

Hahahahahaha... I've been reading this line for god knows how many times and I never stop and think for a second what it meant:

if (drop_vars && node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) {

https://github.com/mishoo/UglifyJS2/blob/9bf72cf75822044ae314b4646db9aefb1bd38284/lib/compress.js#L1973

So yeah, easy fix.

n is used twice within the same variable declaration. I had a similar problem with collapse_vars a year ago.

It's only declared twice - drop_unused() can handle that just fine :wink:

Even collapse_vars wouldn't have trouble with it, as declarations won't be added to the SymbolRef.references array.

if (drop_vars && node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) {

Oh crap, I think I may have introduced that.

As it turns out I was thinking of a different but similar commit I had done: https://github.com/mishoo/UglifyJS2/commit/3ac24219322384539caae803482ea257e7971cf2

In that case you are even safer - that section of code no longer exists on current master :wink:

Was this page helpful?
0 / 5 - 0 ratings

Related issues

alexlamsl picture alexlamsl  路  4Comments

diegocr picture diegocr  路  3Comments

uiteoi picture uiteoi  路  5Comments

chrismanley picture chrismanley  路  5Comments

utdrmac picture utdrmac  路  4Comments