Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve --reduce-test #3722

Merged
merged 1 commit into from
Feb 15, 2020
Merged

improve --reduce-test #3722

merged 1 commit into from
Feb 15, 2020

Conversation

kzc
Copy link
Contributor

@kzc kzc commented Feb 15, 2020

  • hoist body of functions and IIFEs
  • simplify var declarations

@kzc
Copy link
Contributor Author

kzc commented Feb 15, 2020

Example:

$ cat um.js
var c = 10;
function f0(b) {
    var i = 42;
    var c = 3 + function() {
        var x = b.toString();
        a = 0;
        function f(w) {
            var k = .1, l = 5;
            a = i - i + .1 + .1 + .1 + k.toString() + w.toString();
            var c = i = a + 5;
        }
        f(x, 2..toString());
        console.log(a);
        return a;
    }();
}
var b = f0(5);
console.log(c);

without this PR:

$ bin/uglifyjs um.js -c unsafe_math --reduce-test
// reduce test pass 1, iteration 0: 396 bytes
// reduce test pass 1, iteration 25: 224 bytes
// reduce test pass 1, iteration 50: 204 bytes
// reduce test pass 1, iteration 75: 173 bytes
// reduce test pass 1, iteration 100: 137 bytes
// reduce test pass 1: 121 bytes
// reduce test pass 2: 87 bytes
// reduce test pass 3: 87 bytes
function f0() {
    var c = function() {
        function f() {
            a = 1 + .1 + .1;
        }
        f();
        console.log(a);
    }();
}

var b = f0();
// output: 1.2000000000000002
// 
// minify: 1.2
// 
// options: {
//   "compress": {
//     "unsafe_math": true
//   },
//   "mangle": false
// }

with this PR:

$ bin/uglifyjs um.js -c unsafe_math --reduce-test
// reduce test pass 1, iteration 0: 396 bytes
// reduce test pass 1, iteration 25: 220 bytes
// reduce test pass 1, iteration 50: 205 bytes
// reduce test pass 1, iteration 75: 174 bytes
// reduce test pass 1, iteration 100: 157 bytes
// reduce test pass 1, iteration 125: 127 bytes
// reduce test pass 1: 111 bytes
// reduce test pass 2: 70 bytes
// reduce test pass 3: 25 bytes
a = 1 + .1 + .1, console.log(a);
// output: 1.2000000000000002
// 
// minify: 1.2
// 
// options: {
//   "compress": {
//     "unsafe_math": true
//   },
//   "mangle": false
// }

test/exports.js Outdated
@@ -13,3 +13,4 @@ exports["to_ascii"] = to_ascii;
exports["tokenizer"] = tokenizer;
exports["TreeTransformer"] = TreeTransformer;
exports["TreeWalker"] = TreeWalker;
exports["walk_body"] = walk_body;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably should either be a public export, or a method on TreeWalker. Good enough in test for now.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just a helper function that can be replicated, unlike List which is irreplaceable.

In fact, there should be no body instanceof AST_Statement within uglify-js, so I'm going to nuke that for a performance bump.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean removing AST_Statement from uglify-js altogether?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that extreme − just removing that instanceof clause since it's unreachable. 😅

@@ -61,9 +61,6 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)

var parent = tt.parent();

// ignore call expressions
if (parent instanceof U.AST_Call && parent.expression === node) return;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was needed once. Apparently not the case now.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can now compare between runtime exceptions properly, so 0() isn't an issue anymore.

@@ -291,7 +327,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
}

// replace this node
var newNode = new U.parse(REPLACEMENTS[node.start._permute % REPLACEMENTS.length | 0], {
var newNode = U.parse(REPLACEMENTS[node.start._permute % REPLACEMENTS.length | 0], {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed typo. I was surprised this had worked at all with new.

Copy link
Collaborator

@alexlamsl alexlamsl Feb 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new

The new keyword does the following things:

  1. Creates a blank, plain JavaScript object;
  2. Links (sets the constructor of) this object to another object;
  3. Passes the newly created object from Step 1 as the this context;
  4. Returns this if the function doesn't return its own object.

But yeah, I didn't notice that either − sorry 😅

Edit: and it's got to be an object (i.e. array works too) − if I say return 42 it would not have any effects at all.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, that means we can optimise new F(...) ➡️ F(...) as long as:

  • typeof F() == "object" always
  • !F.contains_this()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth a shot. There's always unanticipated found problems in practice. Does ufuzz generate such new cases with and without this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kzc
Copy link
Contributor Author

kzc commented Feb 15, 2020

Tested with the evaluate xor bug test suite (tm) - no speed reduction, and reduced test cases are half the size with this PR.

node.body = [];
body.push(node);
CHANGED = true;
return List.splice(body);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function declaration with a now empty body was pushed after its original (now hoisted) body to prevent the future toplevel function call from failing. These calls do not have typeof checks. The empty function decl and the now useless call will be dropped in the next pass.

test/reduce.js Outdated
return true; // don't descend into nested functions
}
});
U.walk_body(fn, tw);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why a simple fn.walk(tw) won't suffice here?
AFAICT we only need to change node instanceof U.AST_Lambda above to node instanceof U.AST_Scope && node !== fn, which is a common pattern.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had what you described but thought it was a waste of CPU. The walk_body does exactly what I want without the need for such a check.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, there are two calls to walk_body in lib/compress.js.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll make the change you suggested, but it's less efficient.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think walk_body() is an artifact left over from uglify-es, as they have that AST_Arrow madness.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AST_Arrow could have been implemented better in hindsight, but I think there's a legitimate case for walk_body. No matter - I made the changes locally pending testing.

test/reduce.js Outdated
function to_sequence(expressions) {
if (expressions.length == 0) return new U.AST_Number({value: 0, start: {}});
if (expressions.length == 1) return expressions[0];
return new U.AST_Sequence({expressions: expressions, start: {}});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to keep it simple. I ran the PR against dozens of ufuzz induced errors in xor, collapse_vars and reduce_vars and never saw the case you're describing come up. But I'll add the logic you mentioned. It will require retesting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made these changes locally but I am unable to come up with a test to exercise it. I suspect what happened before is that the unneeded sequence elements were just dropped by normal reduce_test operations, and single-element sequences were already converted to a regular expression. Nevertheless, I'll keep the changes and continue to test with it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may be correct that it was only done internally to ensure optimal compression.

Thanks for entertaining my worries nonetheless 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem.

Not much left to optimize in reduce_test. It's good enough.

@kzc
Copy link
Contributor Author

kzc commented Feb 15, 2020

The requested changes were pushed. The xor bug test suite ran the same as before with similar timings. Also ran ufuzz for 10 minutes and it looked fine.

@kzc
Copy link
Contributor Author

kzc commented Feb 15, 2020

Don't merge just yet - adding support for hoisting simple statement IIFE function expression body.

- hoist body of functions and IIFEs
- simplify var declarations
@kzc
Copy link
Contributor Author

kzc commented Feb 15, 2020

Pushed optimization to hoist simple statement IIFEs...

Without this PR:

$ echo 'var a=0; (function(){ a = 2 + .1 + .1 + .1 })(); console.log(a);' | bin/uglifyjs -c unsafe_math --reduce-test
// reduce test pass 1, iteration 0: 65 bytes
// reduce test pass 1, iteration 25: 44 bytes
// reduce test pass 1: 41 bytes
// reduce test pass 2: 41 bytes
// reduce test pass 3: 41 bytes
(function() {
    a = 1 + .1 + .1;
})();

console.log(a);
// output: 1.2000000000000002
// 
// minify: 1.2
// 
// options: {
//   "compress": {
//     "unsafe_math": true
//   },
//   "mangle": false
// }

With this PR:

$ echo 'var a=0; (function(){ a = 2 + .1 + .1 + .1 })(); console.log(a);' | bin/uglifyjs -c unsafe_math --reduce-test
// reduce test pass 1, iteration 0: 65 bytes
// reduce test pass 1, iteration 25: 28 bytes
// reduce test pass 1: 25 bytes
// reduce test pass 2: 25 bytes
// reduce test pass 3: 25 bytes
a = 1 + .1 + .1;

console.log(a);
// output: 1.2000000000000002
// 
// minify: 1.2
// 
// options: {
//   "compress": {
//     "unsafe_math": true
//   },
//   "mangle": false
// }

Side note - I preferred the more succinct single line JSON options output. Oh well.

In testing I encountered a case with a couple of nested loops with a complex loop condition expression taking hundreds of iterations to reduce. It took over 5 minutes to produce a reduced test case. Consider lowering the max_timeout.

It might also be useful to memoize the code and result in a static map for producesDifferentResultWhenMinified to speed up reduction. Sometimes there's a cycle of non-contiguous iterations where the same code is run. I've seen cycles with a dozen variations repeat a few times. Would have to be mindful of the increasing timeout in the memoize map key which could be prepended to code. Something like memoize[timeout + code] = result;. minify_options never change for a given reduce_test run so it doesn't have to appear in the key.

@kzc
Copy link
Contributor Author

kzc commented Feb 15, 2020

When CI finishes it's good to merge. I will be re-retiring from uglify development.

@alexlamsl
Copy link
Collaborator

Indeed the maximum timeout would cause pathological cases to grow in total runtime, however I had frequent trouble with ufuzz output whereby the original timeout value was causing --reduce-test to bail immeidately. So in light of that I trade worst case runtime for reliability.

The static map idea sounds interesting, will investigate in due course.

Thanks again for your contribution, as always 😉

@alexlamsl
Copy link
Collaborator

What's left for me is reading the new and improved daily ufuzz reports 😎

@kzc
Copy link
Contributor Author

kzc commented Feb 15, 2020

A pathological multi-loop xor bug induced test case (not provided) without memoization:

$ time node-v12.13.0 bin/uglifyjs fail-bigcpu.js --toplevel -mc passes=99,unsafe --reduce-test
// reduce test pass 1, iteration 0: 29271 bytes
// reduce test pass 1, iteration 25: 2817 bytes
// reduce test pass 1, iteration 50: 219 bytes
// reduce test pass 1, iteration 75: 205 bytes
// reduce test pass 1, iteration 100: 189 bytes
// reduce test pass 1, iteration 125: 147 bytes
// reduce test pass 1: 115 bytes
// reduce test pass 2: 79 bytes
// reduce test pass 3: 79 bytes
var c = 0;

function f0() {
    do {
        0;
    } while ((1 ^ 1) << (c = c + 1));
}

var foo_2 = f0();

console.log(c);
// output: 1
// 
// minify: 31
// 
// options: {
//   "compress": {
//     "passes": 99,
//     "unsafe": true
//   },
//   "mangle": true,
//   "toplevel": true
// }

real	7m28.845s
user	7m33.994s
sys	0m2.746s

The same test case with the same max timeout with (naive) memoization:

$ time node-v12.13.0 bin/uglifyjs fail-bigcpu.js --toplevel -mc passes=99,unsafe --reduce-test
// reduce test pass 1, iteration 0: 29271 bytes
// reduce test pass 1, iteration 25: 2817 bytes
// reduce test pass 1, iteration 50: 219 bytes
// reduce test pass 1, iteration 75: 205 bytes
// reduce test pass 1, iteration 100: 189 bytes
// reduce test pass 1, iteration 125: 147 bytes
// reduce test pass 1: 115 bytes
// reduce test pass 2: 79 bytes
// reduce test pass 3: 79 bytes
var c = 0;

function f0() {
    do {
        0;
    } while ((1 ^ 1) << (c = c + 1));
}

var foo_2 = f0();

console.log(c);
// output: 1
// 
// minify: 31
// 
// options: {
//   "compress": {
//     "passes": 99,
//     "unsafe": true
//   },
//   "mangle": true,
//   "toplevel": true
// }

real	0m46.345s
user	0m48.897s
sys	0m0.199s

This pathological test case with loops was reduced 10 times faster.

A typical test case reduction is only 20% faster.

The memoization implementation is left as an exercise to the reader.
;-)

@alexlamsl alexlamsl merged commit fbfa617 into mishoo:master Feb 15, 2020
@kzc
Copy link
Contributor Author

kzc commented Feb 16, 2020

Unfortunately, the reduce_test cache in #3725 doesn't help much for the test case above:

$ time node-v12.13.0 bin/uglifyjs fail-bigcpu.js --toplevel -mc passes=99,unsafe --reduce-test
// reduce test pass 1, iteration 0: 29271 bytes
// reduce test pass 1, iteration 25: 2817 bytes
// reduce test pass 1, iteration 50: 279 bytes
// reduce test pass 1, iteration 75: 205 bytes
// reduce test pass 1, iteration 100: 189 bytes
// reduce test pass 1, iteration 125: 137 bytes
// reduce test pass 1: 105 bytes
// reduce test pass 2: 65 bytes
// reduce test pass 3: 50 bytes
var c = 0;

do {
    0;
} while ((1 ^ 1) << (c = c + 1));

console.log(c);
// output: 1
// 
// minify: 31
// 
// options: {
//   "compress": {
//     "passes": 99,
//     "unsafe": true
//   },
//   "mangle": true,
//   "toplevel": true
// }

real	6m17.096s
user	6m19.842s
sys	0m0.299s

The implementation of memoization I had was naïve with no regard to timeout involved - just memoize[code] = result with no cache clearing.

Perhaps the cache implementation timing could be improved by having a coarser grained timeout increment. Or perhaps don't clear the cache under certain constraints.

Here's the original test case:

$ cat fail-bigcpu.js
// !!!!!! Failed... round 4
// original code
// (beautified)
var _calls_ = 10, a = 100, b = 10, c = 0;

function f0(undefined_1) {
    for (var brake1 = 5; undefined_1 && undefined_1.Infinity && brake1 > 0; --brake1) {
        var brake2 = 5;
        do {
            {
                var brake3 = 5;
                do {
                    {
                        {
                            return b += a;
                        }
                        {
                            var expr6 = (c = c + 1) + (typeof undefined_1 == "function" && --_calls_ >= 0 && undefined_1());
                            L10001: for (var key6 in expr6) {
                                c = 1 + c;
                                var a = expr6[key6];
                                {
                                    var brake7 = 5;
                                    while ((c = c + 1) + (typeof f2 == "function" && --_calls_ >= 0 && f2((c = 1 + c, 
                                    ("undefined" && -1) == 1 >> NaN ^ (-3 >>> false) - (Infinity - 5)), (c = 1 + c, 
                                    ("" !== -5) / (undefined_1 && (undefined_1.null = "" && "")) ^ (true >>> Infinity && (undefined_1 = 5 % 3))))) && --brake7 > 0) {
                                        var brake8 = 5;
                                        do {
                                            {
                                                var brake9 = 5;
                                                while ((c = 1 + c, ((undefined_1 >>= 0 != this) | 2 >= "undefined") !== "a" < -5 > (true && -3)) && --brake9 > 0) {
                                                    c = 1 + c, ((undefined_1 && (undefined_1.undefined += /[a2][^e]+$/ + -2)) !== -3 - true) >>> ("object" * "number" > -0 % 3);
                                                }
                                            }
                                        } while ((c = c + 1) + typeof c_1 && --brake8 > 0);
                                    }
                                }
                            }
                        }
                    }
                } while (a++ + +function a() {
                    try {
                        --b + ((c = 1 + c, ~(22, "undefined") > (undefined_1 += -5 << "" && ("c" && 25))) ? (c = 1 + c, 
                        (22 + -2 != NaN + "object") >>> ((undefined_1 = 22 && "") === ("number" && "bar"))) : (c = 1 + c, 
                        ("number", -1) / ("bar" <= "bar") > (38..toString() != 1) % ("function" * Infinity))) ? b += a : void function() {
                        }();
                    } catch (b_1) {
                        {}
                        b += a;
                    }
                    {
                        var expr15 = a++;
                        for (var key15 in expr15) {
                        }
                    }
                    {
                        var a = function f1() {
                            c = c + 1;
                            {
                                var brake19 = 5;
                                do {
                                    c = 1 + c, 24..toString() << 38..toString() << (NaN << 5) != (undefined_1 && (undefined_1.foo = 24..toString() !== 24..toString())) + (Infinity === /[a2][^e]+$/);
                                } while ((c = 1 + c, -38..toString() != ("b" === 23..toString()) ^ (-1 >= 22) / (undefined_1 && (undefined_1.b += Infinity + this))) && --brake19 > 0);
                            }
                        }();
                    }
                }() && --brake3 > 0);
            }
        } while ((c = c + 1) + (1 === 1 ? a : b) && --brake2 > 0);
    }
    try {
        try {
            c = c + 1;
        } finally {
            a++ + ((3 / "object" != void "a") >>> (("b" ^ "object") >= ("a", "b")));
            if ((c = c + 1) + ([ a++ + delete b, a++ + (0 === 1 ? a : b), --b + undefined_1, undefined_1 ] ? undefined_1 : --b + {
                "\t": (c = c + 1) + (typeof f3 == "function" && --_calls_ >= 0 && f3("foo", (c = 1 + c, 
                delete (undefined_1 = 2 - null) > ((undefined_1 && (undefined_1.NaN += [] << 4)) & undefined == "a")))),
                0: /[abc4]/.test((undefined_1 || b || 5).toString()),
                b: typeof bar == "number",
                var: --b + {}.a,
                c: (c = c + 1) + (0 === 1 ? a : b)
            }.undefined)) {
                try {
                    try {
                        {
                            var brake28 = 5;
                            while (--b + a-- && --brake28 > 0) {
                                if ((c = c + 1) + ((c = 1 + c, ((24..toString(), 0) | 5 <= {}) !== (({} | 23..toString()) ^ 25 !== "bar")) ? (c = 1 + c, 
                                (undefined_1 && (undefined_1[~((undefined_1 && (undefined_1.null = Infinity >>> "b"), 
                                "a" || "foo") ^ (c = c + 1, -4 * "function"))] /= ([ , 0 ].length === 2 == []) <= "c" % -1)) << (c = c + 1, 
                                2 - 5)) : (c = 1 + c, ("bar" << "object", undefined ^ 4) >= (undefined_1 && (undefined_1.null &= (false & {}) - (undefined_1 && (undefined_1.var += -1 * -1))))))) {
                                    switch (c = 1 + c, (undefined_1 && (undefined_1[b = a] = (5, []) / ([], 0))) & "" % "" - [] * -0) {
                                      case c = 1 + c, ({} && {}) >= 3 - {} | ("c" & undefined) * ({} === 1):
                                        ;
                                        break;

                                      case c = 1 + c, undefined_1 && (undefined_1.foo = ([] ^ "bar") - -0 / undefined >= (undefined_1 = 23..toString() >> -0 ^ "bar" == 5)):
                                        ;
                                        break;

                                      case c = 1 + c, (23..toString() | 24..toString() && null > "a") % ((this ^ "c") >= (undefined_1 && (undefined_1[c = 1 + c, 
                                        undefined_1 && (undefined_1[typeof parseInt_2 === "function"] = 25 << "function" < true >>> "number" == (25 % this !== (25 & "b")))] += 2 != true))):
                                        ;
                                        break;

                                      case c = 1 + c, (-1 % -1 != (2 == 2)) <= (-1 % true <= ("" >= 38..toString())):
                                        ;
                                        break;
                                    }
                                }
                            }
                        }
                    } finally {
                        {
                            var brake31 = 5;
                            do {
                                c = 1 + c, (3 / ([ , 0 ].length === 2), "bar" + {}) | (true && "foo") >= +"object";
                            } while ((undefined < 5 ^ (1 ^ {})) << ("a" * "number" ^ (c = c + 1, /[a2][^e]+$/)) && --brake31 > 0);
                        }
                        c = c + 1;
                    }
                } catch (b_1) {
                    return ~("c" >> -0) % ((-5 + 2) % ("b" ^ "object"));
                    {
                        var NaN_2 = function f2(b, arguments_1) {
                            c = 1 + c, delete (NaN <= 24..toString()) % ((NaN === 5) / ([ , 0 ].length === 2 & "undefined"));
                            c = 1 + c, (b_1 %= ("number" !== 4) * (-0 && {})) != (true ^ 38..toString() || null & 25);
                        }(null, (c = 1 + c, b_1 && (b_1[this >= undefined || (4 || 23..toString()), "b" / 5 * (undefined_1 /= 1 >>> 38..toString())] += ("foo" === "undefined") % (23..toString() * 25) >= (b_1 && (b_1.Infinity = (0 > 5) * (5 || this))))), (c = 1 + c, 
                        (undefined_1 && (undefined_1[c = 1 + c, NaN_2 && (NaN_2[[ (c = 1 + c, ((c = c + 1, 
                        "foo") ^ (/[a2][^e]+$/ && "b")) < (true << "function") % (-1 & -1)), (c = 1 + c, 
                        ((c = c + 1, []) >>> "" * "object") - (~([ , 0 ].length === 2) + (0 > "function"))) ].NaN] += (undefined_1 += 38..toString() == this, 
                        c = c + 1, "function") >= (NaN_2 *= NaN + 25) % (([ , 0 ].length === 2) - "number"))] = [] == "bar")) ^ "" !== NaN && "number" >> [] ^ NaN != -1));
                    }
                } finally {
                    c = c + 1;
                    {
                        var NaN_2_1 = (c = 1 + c, this >> "foo" >= true >>> -0 != (NaN_2 && (NaN_2[a++] = -4 == this ^ (-5 ^ 0)))), b = (c = 1 + c, 
                        c = c + 1, ([ , 0 ][1] && []) > 3 << 1);
                        if (c = 1 + c, (25 + "foo" | "a" ^ []) << ("undefined" + 4 | /[a2][^e]+$/ % 25)) {
                            c = 1 + c, NaN_2_1 = (false ^ 25 | undefined > Infinity) & ([] >>> -2) % ("number" / true);
                        } else {
                            c = 1 + c, NaN < 5 == (23..toString() || {}), NaN_2 += 22 / "object" <= (undefined < -0);
                        }
                    }
                }
            }
        }
    } catch (b) {
        if (undefined_1 && (undefined_1[--b + ~((0 && -4 || (c = c + 1, "bar")) + (("", 
        22) - (-3 << false)))] = ("" === Infinity) % (3 < 25) * ((c = c + 1, 23..toString()) ^ "c" & 23..toString()))) {
            switch (a++ + (typeof f4 == "function" && --_calls_ >= 0 && f4("b", b = a))) {
              default:
                {
                    var expr46 = --b + {
                        in: [ , (c = 1 + c, (undefined_1 && (undefined_1.b %= 38..toString() % undefined ^ (NaN, 
                        ""))) >= (NaN_2_1 && (NaN_2_1[a++ + typeof (c = 1 + c, NaN_2 && (NaN_2.foo += (NaN_2 && (NaN_2.in = ("bar", 
                        -5) ^ 23..toString() - false)) - undefined % 38..toString() * (1 % -0)))] >>>= 4 + 2 + "object" * NaN))), (c = 1 + c, 
                        void Infinity - (4 < -0) >= (NaN % true <= +"b")) ]
                    };
                    for (var key46 in expr46) {
                        c = 1 + c;
                        var undefined_1 = expr46[key46];
                        c = c + 1;
                    }
                }

              case +function() {
                    {
                        var expr48 = [ (c = 1 + c, (-5 / "b" || -1 && "b") !== (([ , 0 ][1] | 3) == (22 !== -2))), (c = 1 + c, 
                        (undefined + 25, 3 > "undefined") ^ (5 > "function") + (38..toString() ^ "object")) ][(c = 1 + c, 
                        ("number" && "b" && "b" ^ "b") - (null >>> 38..toString() > "foo" >> "b")) ? (c = 1 + c, 
                        (("object" ^ 25) === 38..toString() << -2) >>> void (-5 != 25)) : (c = 1 + c, ([ , 0 ][1] / "a" < ("number" | [])) % ((NaN_2_1 = true << "number") - ("foo" == "undefined")))];
                        L10002: for (var key48 in expr48) {
                            c = 1 + c;
                            var undefined = expr48[key48];
                            {
                                var brake49 = 5;
                                do {
                                    if (c = 1 + c, null * ([ , 0 ].length === 2) >= -5 / "bar" && "a" >> -3 >= (2 == -2)) {
                                        c = 1 + c, NaN / Infinity % (-3 - [ , 0 ][1]) % ((NaN_2_1 && (NaN_2_1.var = "c" - -4)) === (/[a2][^e]+$/ === /[a2][^e]+$/));
                                    }
                                } while (--b + undefined_1 && --brake49 > 0);
                            }
                        }
                    }
                    {
                        var expr52 = b++;
                        L10003: for (var key52 in expr52) {
                            {
                                c = 1 + c, (2 & [ , 0 ][1] && {} + 1) ^ (undefined_1 && (undefined_1.c += (24..toString(), 
                                "a"))) & (null, 4);
                            }
                        }
                    }
                }():
              case (c = c + 1) + [ --b + b-- ]:
                {
                    return;
                    {
                        var NaN_2 = function b_2(parseInt_1) {
                        }();
                    }
                }
                break;

              case (NaN_2 += "bar" && -3) - (NaN ^ "undefined") >> (2 == 3 != (NaN_2_1 && (NaN_2_1[c = 1 + c, 
                (0 < "object" === true >= false) >= (NaN_2_1 && (NaN_2_1[c = 1 + c, void (2 >> "c") | (NaN_2_1 && (NaN_2_1.null = 24..toString() == "function" != {} >= "foo"))] %= "foo" ^ 22)) + (NaN_2_1 && (NaN_2_1[c = 1 + c, 
                (([] === this) > ~22) >>> (25 - false > delete Infinity)] >>>= "a" < -3))] = false - "c"))):
                if (1 === 1 ? a : b) {
                    var expr59 = a++;
                    L10004: for (var key59 in expr59) {
                        {
                            var expr60 = NaN_2;
                            L10005: for (var key60 in expr60) {
                                c = 1 + c;
                                var Infinity_2 = expr60[key60];
                                var a = (c = 1 + c, undefined_1 += (!-3 ^ !2) >> (null < 24..toString() >= 1 + "number")), Infinity_2_2 = (c = 1 + c, 
                                ("object" & []) >>> (Infinity_2_2 && (Infinity_2_2[c = 1 + c, c = c + 1, void {} >>> -0 * 4] += Infinity << -3)) ^ void this + (NaN_2_1 && (NaN_2_1.var |= {} == NaN)));
                            }
                        }
                    }
                }
                break;
            }
        }
        {
            return;
            [ undefined_1, [ a++ + (1 === 1 ? a : b), a++ + new function b_2() {
                this.a = (4, "function") | ~1;
                c = 1 + c, ("undefined" < -5) + (2 && -0) ^ (true + -0 ^ (Infinity_2_2 = 0 ^ "function"));
                c = 1 + c, ("number" & 24..toString()) - ("a" <= 22) == (true > "") / (Infinity_2 = "undefined" | NaN);
            }(), a++ + +([] / null != -0 % [ , 0 ][1] === null < "b" < 38..toString() / 3) ][typeof Infinity_2_2 == "function" && --_calls_ >= 0 && Infinity_2_2(+function() {
            }(), [ , 0 ].length === 2)], (c = c + 1) + {
                var: a++ + void function b() {
                    c = 1 + c, ({} != this) > (Infinity_2_2 && (Infinity_2_2[c = 1 + c, Infinity_2_2 && (Infinity_2_2.var = (NaN | null) + 0 * -1 !== ("bar" == false & (Infinity_2_2 && (Infinity_2_2.var += 2 != false))))] |= "bar" * [ , 0 ][1])) && Infinity == "object" !== "b" >> ([ , 0 ].length === 2);
                    c = 1 + c, (Infinity_2_2 && (Infinity_2_2.a += (c = c + 1, 25) == (null == NaN))) % (undefined_1 && (undefined_1[b--] |= 5 + "function" & 24..toString() === ([ , 0 ].length === 2)));
                    c = 1 + c, ("b" | "") - (24..toString() >> -4) == (4 === 0) + (24..toString() <= "function");
                }()
            }[a++ + {
                c: (c = 1 + c, ("bar" >= "" & (c = c + 1, [ , 0 ].length === 2)) >> (1 >= 23..toString() < (this || "number"))),
                3: (c = 1 + c, NaN_2_1 && (NaN_2_1[a++ + (b = a)] = (!1 <= (/[a2][^e]+$/ != ""), 
                -4 + 23..toString() || /[a2][^e]+$/ && -4))),
                undefined: (c = 1 + c, (Infinity_2 = 1 / -2 & (NaN || -4)) > (22 | 22 | true + undefined)),
                "-2": (c = 1 + c, c = c + 1, (undefined_1 = -4 << "c") >>> ("c" << NaN)),
                foo: (c = 1 + c, c = c + 1, "bar" === null && 0 === 5)
            }[--b + (b -= a)] ? b-- : a--], [ --b + (typeof undefined_1 == "function" && --_calls_ >= 0 && undefined_1(24..toString(), [ , 0 ][1])), a++ + (1 === 1 ? a : b), typeof NaN_2_1 !== "crap", (c = c + 1) + ((c = c + 1) + a--), [ (c = 1 + c, 
            (undefined_1 && (undefined_1.Infinity = (1 || 38..toString()) !== (Infinity_2_2 && (Infinity_2_2[c = 1 + c, 
            ([] | "c" | Infinity >= "bar") >> ({} | -4) * (NaN_2_1 += "" >> "a")] = 2 - -0)))) < (38..toString() != undefined) / (Infinity_2_2 && (Infinity_2_2.NaN = "number" > {}))), (c = 1 + c, 
            "c" - [ , 0 ][1] > (Infinity_2_2 && (Infinity_2_2[c = 1 + c, Infinity_2 = ("foo" < 2 <= (-0 === 23..toString())) >>> ([ , 0 ].length === 2, 
            "foo") * ([] & 4)] >>= null && "foo")) ^ 5 > true !== (c = c + 1, [ , 0 ].length === 2)) ].foo ].var, a++ + (((NaN_2 && (NaN_2.a += 0 * "c")) == 23..toString() <= "undefined") >> ((23..toString() !== []) >> "b" + /[a2][^e]+$/)) ][typeof f3 == "function" && --_calls_ >= 0 && f3(NaN_2_1)];
        }
    } finally {
        switch (--b + (((38..toString() != null) > Infinity >> 23..toString()) >>> ((c = c + 1, 
        "number") << (c = c + 1, false)))) {
          default:
          case typeof undefined_1 == "function" && --_calls_ >= 0 && undefined_1(--b + (NaN_2 && NaN_2[(c = c + 1) + (typeof f4 == "function" && --_calls_ >= 0 && f4())])):
            {
                var expr69 = {
                    "": Infinity_2_2 && Infinity_2_2[--b + !b],
                    b: +a,
                    get null() {
                        c = c + 1;
                        {
                            return false;
                        }
                    },
                    c: Infinity_2 %= --b + undefined_1
                }[--b + Infinity_2];
                for (var key69 in expr69) {
                    c = 1 + c;
                    var bar = expr69[key69];
                    for (var brake72 = 5; a++ + /[abc4]/.test(((c = c + 1) + (((c = c + 1, "object") ^ -3 + 23..toString()) !== (([ , 0 ].length === 2) >> 38..toString()) / delete "undefined") || b || 5).toString()) && brake72 > 0; --brake72) {}
                }
            }
            Infinity_2_2 %= --b + {
                undefined: delete (25 / 5 / (4 != {}) === (-"function" !== (c = c + 1, 3))),
                var: a++ + (b = a),
                1.5: a++ + {
                    NaN: (c = 1 + c, (this != 2, Infinity_2_2 && (Infinity_2_2[c = 1 + c, Infinity_2_2 && (Infinity_2_2.Infinity &= (Infinity_2_2 && (Infinity_2_2[{}[c = 1 + c, 
                    "foo" - NaN != false / "number" | (undefined_1 && (undefined_1[b--] = ("function" & NaN) - 24..toString() / "a"))]] = ({}, 
                    3) ^ undefined < {})) / void (23..toString() ^ "foo"))] = /[a2][^e]+$/ >>> 2)) + ((Infinity_2 = -1 !== "c") ^ delete "bar"))
                }[c = 1 + c, (null != -2 && -2 & 25) >> 4 * "" * (null > "number")],
                "": --b + ~a
            };
            break;

          case b = a:
            {
                var brake75 = 5;
                do {
                    try {
                        {
                            var brake77 = 5;
                            L10006: do {
                                {
                                    var brake78 = 5;
                                    do {
                                        {
                                            var expr79 = (c = 1 + c, "bar" >> "number" === undefined < -4 ^ "undefined" < 3 <= (0, 
                                            true));
                                            for (var key79 in expr79) {
                                                c = 1 + c, c = c + 1, (Infinity ^ Infinity) << -1 + 5;
                                            }
                                        }
                                    } while (--b + (Infinity_2_2 && Infinity_2_2[c = 1 + c, "object" - 5 - (null > undefined) >> ((-2 == "object") <= (null == [ , 0 ][1]))]) && --brake78 > 0);
                                }
                            } while (--b + "number" && --brake77 > 0);
                        }
                    } finally {
                        try {
                            if (c = 1 + c, (NaN < "bar") >> (5 | "a") | ((NaN_2_1 = Infinity ^ "b") && 25 * 38..toString())) {
                                c = 1 + c, (2 & "undefined") >> (22 >= 2) == (38..toString() ^ "b") > (0 & null);
                            }
                        } finally {
                            c = 1 + c, ([] && 22) <= /[a2][^e]+$/ + [] | "function" >>> -1 << (25 && 2);
                            c = 1 + c, (5 & "a" || -3 == "c") % ((Infinity_2_2 -= this & this) >= -5 / Infinity);
                        }
                        {
                            var brake86 = 5;
                            while (typeof Infinity_2_2 == "function" && --_calls_ >= 0 && Infinity_2_2(1, (c = 1 + c, 
                            4 * -4 > (25 > 25) > ("foo" + 5) / (23..toString() | 1))) && --brake86 > 0) {
                            }
                        }
                    }
                } while (Infinity_2 && Infinity_2.a && --brake75 > 0);
            }

          case (c = c + 1) + (--b + !function() {
                {
                    var expr88 = 0 === 1 ? a : b;
                    L10007: for (var key88 in expr88) {
                        var bar_2 = a++ + /[abc4]/.test(((c = 1 + c, (NaN_2 && (NaN_2[c = 1 + c, (c = c + 1, 
                        void -1) != (c = c + 1, -4) - (-1 ^ false)] ^= 24..toString() - 23..toString())) - (this < -3) << ((NaN_2_1 && (NaN_2_1.null -= "foo" & 23..toString())) | "c" === true)) || b || 5).toString()), a_2 = {
                            "\t": (c = 1 + c, c = c + 1, ("" == "c") * (null, null)),
                            "-2": (c = 1 + c, a_2 && (a_2[--b + (typeof foo_2 === "function")] += 4 << 23..toString() === (NaN ^ 4)), 
                            (this !== 4) >= void -3)
                        }.c;
                    }
                }
                (c = c + 1) + /[abc4]/.test((--b + void a || b || 5).toString());
                return (c = c + 1) + --a;
            }()):
            switch ([ false, --b + "a" ].undefined) {
              case --b + (NaN_2 && NaN_2[delete a]):
                break;

              case (c = c + 1) + NaN_2:
                break;

              case (c = c + 1) + -((-1 >> 22) + (c = c + 1, -5) == (c = c + 1, 38..toString()) >>> ("c" >= "b")):
                {
                    var brake93 = 5;
                    do {
                        {
                            var brake94 = 5;
                            do {
                                c = 1 + c, (this !== "bar") % ([ , 0 ].length === 2 === []) < (this + [ , 0 ][1] | ("foo" | Infinity));
                            } while ({
                                0: (c = 1 + c, ("function" != -2) >>> "" % "foo" >= ("object" === this & (c = c + 1, 
                                {}))),
                                "-2": (c = 1 + c, (null === 23..toString() != (c = c + 1, "foo")) << (Infinity_2_2 && (Infinity_2_2[!b] %= -3 ^ -1 ^ 24..toString() << /[a2][^e]+$/))),
                                3: (c = 1 + c, (5 <= "a" != (0 == ([ , 0 ].length === 2))) >> ((1 & 0) > "function" / [])),
                                NaN: (c = 1 + c, (-5 && -2) != (Infinity_2_2 && (Infinity_2_2[c = 1 + c, (undefined && true) > (4 || 1) && (5 ^ "b") > ("object" & -1)] = "bar" || Infinity)) && Infinity ^ -5 ^ ("a" || "foo"))
                            }[c = 1 + c, 2 * [ , 0 ][1] - (-0 >= Infinity) | ([] && -3) - null / "b"] && --brake94 > 0);
                        }
                    } while (--b + (b += a) && --brake93 > 0);
                }
                a++ + (b = a);
                break;

              default:
                {
                    L10008: for (var brake98 = 5; (c = 1 + c, ("" ^ 38..toString()) * (23..toString(), 
                    -3) - ((0 < [ , 0 ][1]) << ("object" < NaN))) && brake98 > 0; --brake98) {
                        c = 1 + c, c = c + 1, 2 == undefined, {} << 25;
                    }
                    {
                        var expr100 = (c = 1 + c, Infinity_2_2 && (Infinity_2_2[--b + +((5 != 38..toString()) >>> ("a" > undefined) >> ((undefined_1 && (undefined_1.NaN >>>= -0 >> 4)) > ("number" & -5)))] = (Infinity == 4) % ("a" > NaN) >>> (("" == "number") > (-5 & undefined))));
                        L10009: for (var key100 in expr100) {
                            c = 1 + c;
                            var a_2 = expr100[key100];
                            c = 1 + c, (4 >= {} ^ ("b" ^ "")) <= ([ , 0 ].length === 2 == -5) >>> "a" % null;
                        }
                    }
                    L10010: {
                    }
                }
            }
            break;
        }
        L10011: for (var brake103 = 5; --b + ([ typeof f2 == "function" && --_calls_ >= 0 && f2() ] ? b = a : --b + !function a_2() {}()) && brake103 > 0; --brake103) {
            if (--b + [][(c = c + 1) + b--]) {
                for (var brake105 = 5; undefined_1 && brake105 > 0; --brake105) {
                    try {
                        {
                            return;
                            typeof f4 == "function" && --_calls_ >= 0 && f4((c = 1 + c, (Infinity_2_2 && (Infinity_2_2[c = 1 + c, 
                            ((undefined_1 && (undefined_1.foo = null != -4)) != ([ , 0 ].length === 2 === -0)) / (undefined <= 0 | (NaN_2 = -5 && 22))] = true >> 2)) != ("undefined" === NaN) !== (-0 ^ 3) >> (-5 ^ 3)), 3, 23..toString());
                        }
                    } catch (a_2) {
                        {
                            var expr108 = {
                                length: (c = 1 + c, delete (NaN_2 ^= (undefined ^ undefined) >>> (0 < false))),
                                "\t": (c = 1 + c, null === "foo" && "object" < 25 && (undefined & "bar") >>> (/[a2][^e]+$/ <= undefined)),
                                "": (c = 1 + c, ([ , 0 ][1] * -1 | (0 | "b")) === (a_2 && (a_2[1 === 1 ? a : b] ^= (-1 << "undefined", 
                                null / "bar")))),
                                a: (c = 1 + c, (a_2 && (a_2[delete (-4 >> 24..toString() >> (4 < -5) | ("bar" !== true) < (5 < 5))] = (c = c + 1, 
                                {}) + ("number", 4))) & (Infinity_2 && (Infinity_2.c = ({} | 25) - (2 <= "a")))),
                                c: (c = 1 + c, Infinity_2 *= (38..toString() === NaN) % (NaN << 22) < (false >= [ , 0 ][1] >= (-3 & 1)))
                            }.length;
                            for (var key108 in expr108) {
                                L10012: {
                                }
                            }
                        }
                        {
                            c = 1 + c, (NaN_2 && (NaN_2.NaN = false % this >>> ([] & -5))) ^ (c = c + 1, "object") - (c = c + 1, 
                            4);
                            c = 1 + c, (undefined > 3 & Infinity > "c") >> delete ({} + 22);
                        }
                    } finally {
                        try {
                            L10013: for (var brake114 = 5; (c = 1 + c, (-5 != 1 === ~[ , 0 ][1]) >>> 23..toString() % {} % ([] <= 24..toString())) && brake114 > 0; --brake114) {
                                c = 1 + c, ((NaN & "number") != ("function" == "bar")) > (this >> "function" === (NaN_2 &= 24..toString() >> 23..toString()));
                            }
                        } catch (a_2) {
                            c = 1 + c, (/[a2][^e]+$/ > Infinity) * ([] > this) !== /[a2][^e]+$/ >= "undefined" >= ("" > "undefined");
                            c = 1 + c, ((Infinity_2 && (Infinity_2[c = 1 + c, +delete (Infinity + 38..toString())] >>= -0 > ([ , 0 ].length === 2))) <= 24..toString() >>> false) >> (4 & []) + ("number" !== "undefined");
                        } finally {
                            c = 1 + c, ((undefined_1 && (undefined_1.foo += -1 / {})) < (/[a2][^e]+$/ > undefined)) * (-5 / -4 !== "" <= Infinity);
                            c = 1 + c, (c = c + 1, "b" | 24..toString()) * (NaN_2_1 && (NaN_2_1.null &= (undefined_1 = -3 ^ "foo") <= {} / true));
                        }
                        if ((c = c + 1) + (b += a)) {
                            c = c + 1;
                        } else {
                            var brake122 = 5;
                            while ((c = 1 + c, (NaN !== -2) >>> (Infinity ^ undefined) << (NaN_2_1 = (Infinity_2 = 25 & 5) & 23..toString() << "c")) && --brake122 > 0) {
                                c = 1 + c, (/[a2][^e]+$/ >> -0 != -1 >>> null) <= ({} & "" || 24..toString() !== ([ , 0 ].length === 2));
                            }
                        }
                    }
                }
            }
        }
    }
}

var foo_2 = f0((c = c + 1) + false);

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

Here's the compressor bug needed to reproduce the problem:

--- a/lib/compress.js
+++ b/lib/compress.js
@@ -3221 +3221 @@ merge(Compressor.prototype, {
-              case "^"  : result = left ^   right; break;
+              case "^"  : result = left +   right; break; //XXX intentionally introduced bug

@alexlamsl
Copy link
Collaborator

I seem to have reduced that case of yours down to something much simpler:

var c = 0;
do {
    0;
} while ((1 ^ 1) << (c = c + 1));
function f0(undefined_1) {}
f0();
console.log(c);

Would you grateful if you can perform sanity check for me − because if this is the case I can come up with a mocha test alongside a solution to this issue.

@kzc
Copy link
Contributor Author

kzc commented Feb 16, 2020

That's sufficient to reproduce the problem with c13caf4 with xor bug patch:

$ cat fail-simpler.js 
var c = 0;
do {
    0;
} while ((1 ^ 1) << (c = c + 1));
function f0(undefined_1) {}
f0();
console.log(c);
$ cat fail-simpler.js | time node-v12.13.0 bin/uglifyjs -c toplevel,passes=9,unsafe --reduce-test
// reduce test pass 1, iteration 0: 107 bytes
// reduce test pass 1, iteration 25: 81 bytes
// reduce test pass 1, iteration 50: 81 bytes
// reduce test pass 1, iteration 75: 65 bytes
// reduce test pass 1: 65 bytes
// reduce test pass 2: 50 bytes
// reduce test pass 3: 50 bytes
var c = 0;

do {
    0;
} while ((1 ^ 1) << (c = c + 1));

console.log(c);
// output: 1
// 
// minify: 31
// 
// options: {
//   "compress": {
//     "toplevel": true,
//     "passes": 9,
//     "unsafe": true
//   },
//   "mangle": false
// }
      672.62 real       672.79 user         0.28 sys

Strangely it's even slower than the original case WITHOUT the result cache.

@alexlamsl
Copy link
Collaborator

Thanks − will investigate and fix in a few hours.

I think the way to deal with this is to record the original testcase runtime, then if the minified runtime exceeds 100x of that, treat it as failure rather than a timeout to retry.

@kzc
Copy link
Contributor Author

kzc commented Feb 16, 2020

fyi, using the following patch on c13caf4 may be sufficient:

--- a/test/reduce.js
+++ b/test/reduce.js
@@ -20,7 +20,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
     minify_options = minify_options || { compress: {}, mangle: false };
     reduce_options = reduce_options || {};
     var max_iterations = reduce_options.max_iterations || 1000;
-    var max_timeout = reduce_options.max_timeout || 15000;
+    var max_timeout = reduce_options.max_timeout || 10000;
     var verbose = reduce_options.verbose;
     var minify_options_json = JSON.stringify(minify_options, null, 2);
     var timeout = 1000; // start with a low timeout
@@ -406,7 +406,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
                         // can't trust the validity of `code_ast` and `code` when timed out.
                         // no harm done - just ignore latest change and continue iterating.
                         if (timeout < max_timeout) {
-                            timeout += 250;
+                            timeout += 5000;
                             result_cache = Object.create(null);
                         }
                     } else if (diff.error) {
$ cat fail-simpler.js | time node-v12.13.0 bin/uglifyjs -c toplevel,passes=9,unsafe --reduce-test
// reduce test pass 1, iteration 0: 107 bytes
// reduce test pass 1, iteration 25: 81 bytes
// reduce test pass 1, iteration 50: 81 bytes
// reduce test pass 1, iteration 75: 65 bytes
// reduce test pass 1: 65 bytes
// reduce test pass 2: 50 bytes
// reduce test pass 3: 50 bytes
var c = 0;

do {
    0;
} while ((1 ^ 1) << (c = c + 1));

console.log(c);
// output: 1
// 
// minify: 31
// 
// options: {
//   "compress": {
//     "toplevel": true,
//     "passes": 9,
//     "unsafe": true
//   },
//   "mangle": false
// }
      201.99 real       202.25 user         0.12 sys

@alexlamsl
Copy link
Collaborator

Oops.... beautify comments quirks!

$ uglifyjs test.js -c unsafe_math --reduce-test
// reduce test pass 1, iteration 0: 86 bytes
// reduce test pass 1, iteration 25: 50 bytes
// reduce test pass 1, iteration 50: 50 bytes
// reduce test pass 1, iteration 75: 50 bytes
// reduce test pass 1: 49 bytes
// reduce test pass 2: 49 bytes
// reduce test pass 3: 49 bytes
var a = 1, b = 1;

while (a + 1 > a) {
    a *= 2;
}

while (a++ + (1 - b) < a) {
    0;
    // output:
    // minify: Error: Script execution timed out after 15000ms
    // options: {
    //   "compress": {
    //     "unsafe_math": true
    //   },
    //   "mangle": false
    // }
}

@alexlamsl alexlamsl mentioned this pull request Feb 17, 2020
@kzc
Copy link
Contributor Author

kzc commented Feb 23, 2020

Amusing unsafe_math false positive:

// reduced test case (output will differ)
var NaN_1 = /[abc4]/g.exec((22 * (4 / 24..toString()) / 5).toString());
NaN_1[0];
// output: TypeError: Cannot read property '0' of null
// minify: 
// options: {
//   "compress": {
//     "keep_fargs": false,
//     "passes": 1000000,
//     "sequences": 1000000,
//     "unsafe": true,
//     "unsafe_Function": true,
//     "unsafe_math": true,
//     "unsafe_proto": true,
//     "unsafe_regexp": true
//   }
// }
$ node
> NaN_1=/[abc4]/g.exec(0.7333333333333334.toString()),NaN_1[0]
'4'
> NaN_1=/[abc4]/g.exec(0.7333333333333333.toString()),NaN_1[0]
TypeError: Cannot read property '0' of null

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants