Uglifyjs: Bug in `pure_getters`

Created on 9 Jun 2017  路  12Comments  路  Source: mishoo/UglifyJS

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

({
    undefined: {
        foo: ++a
    }[+a],
    undefined: b++,
    NaN: a--,
    3: (c = c + 1) + (--b + (delete (a && (a.NaN ^= -3 << null)) || (true && "number") | -2 == -3) ? a && a.foo : (c = c + 1) + b++)
}).in;

{
    var brake2 = 5;
    while (a++ + {
        0: --b + {
            0: !b,
            c: (c = c + 1) + +function bar() {
                switch ((b + 1 - .1 - .1 - .1 || 9).toString()[a++ + [ (c = 1 + c, (22 << 3 & 3 - 38..toString()) < (void -1 && undefined | "bar")), (c = 1 + c,
                ("object" >= NaN) * (-5, "function") / ((4 || "number") / (22 <= 1))) ]]) {
                  case a++ + typeof ((c = c + 1) + ~((/[a2][^e]+$/ === 23..toString()) - (a && (a[(c = 1 + c,
                    (-1 | "bar") + 0 % "number" ^ [] > 22 !== (-4 || 5))] = 38..toString() ^ -2)) < (!"undefined" !== 4 << undefined))):
                  case {
                        3: (c = c + 1) + (a && a.NaN)
                    }.NaN:
                    c = c + 1;
                    break;

                  default:
                    {
                        var a_2 = function f0() {
                        }((c = 1 + c, -3 != "function" ^ 4 - 0 ^ +(-2 | -4)));
                    }
                    ;

                  case !((c = c + 1, -2 * undefined) >= (({} || "object") & 38..toString() !== null)):
                    break;
                }
                try {
                    var bar = --b + {
                        c: (c = 1 + c, undefined / undefined ^ [] <= Infinity ^ (c = c + 1, false | -1)),
                        1.5: (c = 1 + c, ~3 - (2 >> 3) >> ((c = c + 1, /[a2][^e]+$/) ^ 22 < 1)),
                        Infinity: (c = 1 + c, ((24..toString(), 22) && 38..toString() >>> null) === ("undefined" <= [ , 0 ][1]) >>> ({} >= [ , 0 ][1])),
                        undefined: (c = 1 + c, {} !== -0 == undefined % 4 === ((a_2 = "undefined" && []) ^ (a_2 && (a_2[(c = 1 + c,
                        c = c + 1, (/[a2][^e]+$/ && -4) >>> (a_2 && (a_2.null = "number" >= 1)))] |= {} & NaN))))
                    }.a;
                } catch (b_2) {
                    {
                        var brake10 = 5;
                        L68044: do {
                        } while (--b + (a_2 && a_2[(c = 1 + c, (false && 2) / (24..toString() % [ , 0 ][1]) / ((-5 == true) + (b_2 && (b_2.undefined += 2 > 23..toString()))))]) && --brake10 > 0);
                    }
                    return --b + {
                        1.5: (c = 1 + c, "bar" * undefined >= ("foo" & undefined) > (~false, "object" >= 4)),
                        foo: (c = 1 + c, ("foo" <= "bar") % (b_2 && (b_2.a = -0 >>> 4)) !== (c = c + 1,
                        38..toString()) > delete 0)
                    }[(c = 1 + c, ([] !== "bar") / ("function" | -1) < (([ , 0 ].length === 2) > [ , 0 ][1]) * (/[a2][^e]+$/ | 24..toString()))];
                }
                {
                    var foo_1 = (c = c + 1) + {
                        "": (c = 1 + c, ~(({} ^ "function") & -3 + [ , 0 ][1])),
                        NaN: (c = 1 + c, (-2 !== [ , 0 ][1] ^ -3 << "") >= ("number" + 2) % (-2 >= 5)),
                        foo: (c = 1 + c, NaN === null != (3 ^ 22) & (true - true) * (-1 & 22))
                    }.b;
                }
            }(),
            1.5: ++a
        },
        get in() {
            c = c + 1;
            return (c = c + 1) + {
                Infinity: (c = c + 1) + {
                    foo: (c = c + 1) + +function undefined_1() {
                        for (var brake17 = 5; (c = 1 + c, (undefined_1 += "foo" <= /[a2][^e]+$/) >= {} >>> NaN == (undefined_1 && (undefined_1[(c = 1 + c,
                        "bar" >> /[a2][^e]+$/ == (undefined_1 && (undefined_1[(c = 1 + c, (undefined_1 && (undefined_1.undefined = (true && "function") < "object" % -1)) == (-4 > 0 !== ({} != -3)))] += [] >> 4)) || "foo" & NaN ^ (Infinity ^ -3))] <<= 1 & -3)) < (undefined_1 >>>= 1 >> 23..toString())) && brake17 > 0; --brake17) {
                            c = 1 + c, ~true <= (undefined_1 && (undefined_1.var += "object" - 4)) < (undefined / [] < ("object",
                            24..toString()));
                        }
                        if (c = 1 + c, undefined_1 && (undefined_1[(-1 | 23..toString()) * (undefined_1 += 25 != NaN) % (("foo" != "undefined") / (25 ^ -0))] %= ("bar" > 23..toString()) % (c = c + 1,
                        2) && (1 < -1) - ("function", 23..toString()))) {
                            c = 1 + c, ((undefined_1 && (undefined_1.Infinity = 25 % /[a2][^e]+$/)) ^ (c = c + 1,
                            -0)) >= (22 && "foo" && -4 ^ true);
                        } else {
                            c = 1 + c, (-4 > 0) % (38..toString() >> "foo") | ({} << -2 && 5 | "number");
                        }
                        c = c + 1;
                        {
                            var brake23 = 5;
                            L68045: do {
                                c = 1 + c, undefined_1 && (undefined_1[{
                                    foo: (c = 1 + c, undefined_1 && (undefined_1[--b + ~(((25 ^ /[a2][^e]+$/) & ([ , 0 ].length === 2 | 23..toString())) - ((22 == 3) >>> (3 !== Infinity)))] = !(-5 === "bar") & !(undefined_1 = 5 - 0))),
                                    c: (c = 1 + c, null <= -5 >= "object" >> 1 === (c = c + 1, -2) - (NaN << null)),
                                    in: (c = 1 + c, (-5 / "function" ^ (-3, "foo")) == (undefined_1 = NaN * null % (25 >>> 38..toString())))
                                }[--b + a++]] /= (undefined_1 = 3 > 5 != "" % /[a2][^e]+$/) << (undefined_1 && (undefined_1[b = a] >>= [ , 0 ][1] - 1 ^ "number" / "")));
                            } while ((c = 1 + c, (25 >> -2 & "bar" > 2) >>> (("bar" && [ , 0 ][1]) == (/[a2][^e]+$/ == 22))) && --brake23 > 0);
                        }
                    }(),
                    "-2": a++ + ({
                        b: (c = 1 + c, (c = c + 1, 23..toString()) - (-5 && -3) - (Infinity - "function" != /[a2][^e]+$/ < 3)),
                        undefined: (c = 1 + c, (-1 | "bar" | (c = c + 1, 4)) & "foo" > 3 < ([ , 0 ][1] != 0))
                    }[(c = c + 1) + ((25, undefined) << (-0 < [ , 0 ][1])) / -([] << 3)] || 9).toString()[--b + (a && a.a)],
                    1.5: true
                }.a,
                1.5: 1 === 1 ? a : b,
                a: --b + /[abc4]/.test((--b + +function() {
                    c = c + 1;
                    switch (b++) {
                      case a && a.undefined:
                        c = 1 + c, ("" | NaN) / (a && (a.undefined = 25 ^ "function")) > (22 || 38..toString()) >>> "function" + 22;
                        c = 1 + c, ([ , 0 ][1] >= 22 || /[a2][^e]+$/ && "undefined") != (c = c + 1, null >>> "bar");
                        break;

                      case (c = 1 + c, ((true ^ -5) === (a = 4 && "undefined")) - ([] / "" < (undefined < 1))) ? (c = 1 + c,
                        -1 % 24..toString() >>> 22 / 1 <= (-1 == "foo") >> ("function" & -0)) : (c = 1 + c,
                        (c = c + 1, 23..toString() != ([ , 0 ].length === 2)) !== (-4 < "number" !== 23..toString() >> 1)):
                        c = 1 + c, a = (-2 + -2) % (NaN > true) ^ ({} == 24..toString() ^ undefined >> -2);
                        break;

                      case typeof (c = 1 + c, (delete "number" < ([] == -4)) + (-0 != "number" | 4 / 22)):
                        c = 1 + c, ("object" ^ "") + (4 < false) > (-NaN ^ 25 & "foo");
                        c = 1 + c, ((-5 < true) >>> (25 === {})) / ((a && (a.Infinity = 0 && 38..toString())) << (-2 >> NaN));
                        break;

                      default:
                        c = 1 + c, (c = c + 1, a && (a[(c = 1 + c, (undefined || [ , 0 ].length === 2) % (false ^ NaN) || ("" >= null) - +"object")] = "object" - NaN)) ^ false - 24..toString() >>> (0 !== -2);
                        c = 1 + c, 25 === 23..toString() === (-4 == "function") || (true ^ "foo") * (NaN * NaN);
                    }
                    {
                        c = 1 + c, (a += -5 !== 2) * (a && (a.c |= "foo" ^ 25)) === ("object" >>> 0 === ("bar" !== 22));
                        c = 1 + c, a && (a.b += ("", 23..toString()) + ("undefined" && -1) !== (a = 5 <= "function" & (c = c + 1,
                        "")));
                        c = 1 + c, (38..toString(), [], false >> 3) - ((22 != -4) + (2 == 25));
                        c = 1 + c, (true % 24..toString() !== (a && (a.null |= 2 + []))) >= (a && (a.in = ([ , 0 ][1] > undefined) << ("number" | 22)));
                    }
                    {
                        var b = function f1(a_1, parseInt, parseInt_2) {
                        }((c = 1 + c, 38..toString() / null << (true << -4) == ("function" !== "undefined" === 5 / -4)));
                    }
                }() || b || 5).toString())
            }[a && a.b];
        },
        var: --b + new function a_1() {
            if ({
                Infinity: (a_1 = (a_1 = NaN > /[a2][^e]+$/) !== -4 - 38..toString()) == "function" / null < Infinity / ([ , 0 ].length === 2),
                "": --b + ++a,
                "": [ a++, ("foo" < "foo" ^ ("function" ^ "bar")) >= (a_1 = ("number" || {}, -5 >> 23..toString())), --b + ((c = 1 + c,
                (1 <= [ , 0 ][1]) << (5 ^ 22) >= null * 1 / ([] % -0)) || a || 3).toString() ].b,
                in: [ --b + {
                    "": (c = 1 + c, ("object" == 2 !== "foo" + "foo") <= ((-1 ^ "") != (5 == 25))),
                    c: (c = 1 + c, (3 >= 1) - ("undefined" != []) <= ((-2 && []) == ("" ^ undefined))),
                    null: (c = 1 + c, ("function" ^ true) % (23..toString() >>> []) == (NaN != false) % ("object" > [ , 0 ][1]))
                }, a_1 && a_1.c ]
            }[+(((23..toString() && "function") >> ("undefined" === -4)) + ((22 >> [ , 0 ][1]) + +38..toString()))]) {
                --b + {
                    null: (c = c + 1) + (1 === 1 ? a : b),
                    undefined: a++ + void function() {
                        c = 1 + c, ([ , 0 ][1] >= "function") >>> ("number" ^ false) | (-5 > true || "" | [ , 0 ][1]);
                        c = 1 + c, ("bar" >>> -2) + (23..toString() <= {}) << ([ , 0 ].length === 2 === 0,
                        ([ , 0 ].length === 2) <= {});
                        c = 1 + c, (0 + undefined >= (c = c + 1, [ , 0 ][1])) * (3 < {} || 38..toString() < -1);
                    }(),
                    c: --b + {
                        0: (c = 1 + c, ({} - "bar") * ("undefined" <= 38..toString()) || 1 < -5 <= ([ , 0 ].length === 2 == 38..toString())),
                        NaN: (c = 1 + c, (a_1 = 3 << 25) << +"undefined" >> (true | "foo", "undefined",
                        0)),
                        foo: (c = 1 + c, a_1 && (a_1.undefined = 24..toString() + -4 ^ ("foo" | -5) ^ -4 >> [ , 0 ][1] >>> -3 / -1))
                    },
                    "": ++b,
                    1.5: b++
                };
            } else {}
        }(),
        foo: a && a.c,
        a: /[abc4]/.test(((c = c + 1) + +((c = c + 1, /[a2][^e]+$/ !== -4) >> ((1 ^ 38..toString()) !== (c = c + 1,
        false))) || b || 5).toString())
    } && --brake2 > 0) {}
}

console.log(null, a, b, c);
$ node test.js
null 130 -30 136

$ uglifyjs test.js -c | node
null 130 -20 131

$ uglifyjs test.js -c pure_getters=0 | node
null 130 -30 136
bug

All 12 comments

$ cat test.js
var b = 0;
try {
    (g = []) ^ (g[1e9] |= 0);
} catch (x) {
    b = 1;
}
console.log(b);
$ node test.js
1



md5-8cb30cf0fc374c049a575d4dcc1dd7d6



$ bin/uglifyjs test.js -cb | node
0



md5-8cb30cf0fc374c049a575d4dcc1dd7d6



$ bin/uglifyjs test.js -cb
var b = 0;
try {
    g = [], g[1e9] |= 0;
} catch (x) {
    b = 1;
}
console.log(b);

What to do with this?

$ echo '(g = []) - (g[1e9] = 0)' | node -p
[stdin]:1
(g = []) - (g[1e9] = 0)
         ^

RangeError: Invalid string length

It's not technically a bug. When uglified with compress, the binary operator - is not used, so the huge array is not coerced into a string (to be coerced into a number) and an exception is not thrown:

$ echo '(g = []) - (g[1e9] = 0)' | bin/uglifyjs -c
g=[],g[1e9]=0;
$ echo '(g = []) - (g[1e9] = 0)' | bin/uglifyjs -c | node -p
0

So we basically fixed an out-of-memory bug for the JavaScript VM? Almost sounds like a feature right there... :smirk:

Joking aside, I'd vote for letting this one go as a false positive. Personally I'm a bit surprised this is a recoverable runtime error, but then even with Java you can technically catch OOME as well.

I'd vote for letting this one go as a false positive.

Yeah, nothing we can do about this one. Uglify can't possibly know about runtime constraints like this.

Does every fuzzed test case take an hour to simplify or am I just slow?

Does every fuzzed test case take an hour to simplify or am I just slow?

So what I do normally is this:

  • verify that original and uglified cases are reproducible
  • verify that suspected options do produce correct output
  • verify that adding -b bracketize to the above do not alter any outputs
  • uglifyjs test.js -b bracketize -co bad.js
  • uglifyjs test.js -b bracketize -c suspected_option=0 -o good.js
  • open diff view under VSCode for good.js against bad.js

So you don't reduce the test case at all? You only look for the diffs between good and bad?

Yeah, I can see that taking less time.

If there are too many sections of differences, then:

  • replace a section of code from good.js to bad.js
  • cat good.js | node
  • cat bad.js | node
  • if outputs are different, proceed to replace the next section

So you don't reduce the test case at all? You only look for the diffs between good and bad?

I tried that before, but I find that to be quite time consuming and error-prone. So I use my current method above to narrow the code down, then I proceed to make reduced test cases up based on that knowledge.

Interesting. How much time to isolate a fuzzed test bug on average? Not including the fix.

How much time to isolate a fuzzed test bug on average?

Nowadays it takes longer because they tend to be somewhat exotic. But back when test/ufuzz.js and we were hunting around a dozen a day, it takes me ~10 minutes to narrow down each test case.

If only Skynet can parse my comments above and save us all some work...

I think LLVM has some sort of test case reducer:

http://llvm.org/docs/Bugpoint.html
http://blog.llvm.org/2015/11/reduce-your-testcases-with-bugpoint-and.html

Looks semi-automated. Difficult problem to solve.

Was this page helpful?
0 / 5 - 0 ratings