// original code
// (beautified)
var _calls_ = 10, a = 100, b = 10, c = 0;
function f0(Math_2) {
{
var brake1 = 5;
L17179: do {
} while ((1 === 1 ? a : b) && --brake1 > 0);
}
{
var expr3 = (c = c + 1) + (typeof f2 == "function" && --_calls_ >= 0 && f2(--b + (Infinity_1 && Infinity_1[b = a]), 22, (c = c + 1) + {
3: void function NaN_1() {
{
var b_2 = function f1(a_1, a_1, a_1) {
}("bar");
}
}(),
foo: void function a() {
typeof Math_2 == "function" && --_calls_ >= 0 && Math_2();
}(),
in: typeof b != "boolean",
NaN: --b + ((c = c + 1) + -("a" % 4 << (22 || -0) > (null / 23..toString() & this - "")) || a || 3).toString()
}[typeof f3 == "function" && --_calls_ >= 0 && f3(-0, a++ + +function() {
var Infinity_1 = (c = 1 + c, (c = c + 1, 24..toString() > [ , 0 ][1]) % (([ , 0 ][1] | [ , 0 ][1]) >> (NaN || 3))), arguments_1 = (c = 1 + c,
-0 < "" >= true << 22 | "object" ^ "c" ^ "" >> "number");
{
var brake7 = 5;
do {
c = 1 + c, NaN ^ [ , 0 ].length === 2 | false ^ /[a2][^e]+$/ | (this & -5) < false >>> 3;
} while ((c = 1 + c, Infinity !== "a" !== (arguments_1 && (arguments_1[c = 1 + c,
(arguments_1 -= (23..toString() < 0) >> ("foo" >> "")) && -(-4, this)] >>= -0 != 25)) == (4 <= ([ , 0 ].length === 2) || undefined <= [ , 0 ][1])) && --brake7 > 0);
}
for (var brake9 = 5; (c = 1 + c, delete (true << "" <= -1 - true)) && brake9 > 0; --brake9) {
c = 1 + c, ((arguments_1 && (arguments_1[c = 1 + c, true < [ , 0 ][1] < 38..toString() >> null ^ (NaN <= true) >>> (NaN <= "")] += -5 > NaN)) ^ -2 >>> 0) == ("function" - 2 !== (NaN || 5));
}
}(), 2)]));
for (var key3 in expr3) {
c = 1 + c;
var Infinity_1 = expr3[key3];
{
return --b + function foo_2() {
{
var brake12 = 5;
L17180: while (--b + Infinity_1 && --brake12 > 0) {
--b + (foo_2 = a++ + ++a);
}
}
}();
}
}
}
}
var a = f0(3 === null === (a && (a[c = 1 + c, 3 - "c" === (undefined === null) ^ (a && (a[c = 1 + c,
a /= ("" & -5 ^ -0 + "number") + ([] - 22) % ("bar" ^ -2)] = {} >= /[a2][^e]+$/)) / (a += false < "foo")] = -2 / 4)) == (NaN ^ 3,
{} > "foo"), a++ + [ (c = c + 1) + -b, {
b: !function() {
{
var brake14 = 5;
L17181: do {
var b = (c = c + 1) + (("object" >> 4 | (a && (a[c = 1 + c, undefined / "function" * (24..toString() > -0) % (23..toString() ^ 22 | /[a2][^e]+$/ > [ , 0 ][1])] += 23..toString() < -5))) ^ (a && (a[a++ + (1 === 1 ? a : b)] -= "object" * "function" * (NaN << null))));
} while (--b + {
var: (c = 1 + c, c = c + 1, [ , 0 ][1] / null >>> (a += -0 === 22)),
b: (c = 1 + c, (0, 5) != "undefined" * 22 === ("a" * 38..toString() ^ 1 >>> -5))
}.undefined && --brake14 > 0);
}
}(),
foo: --b,
3: Infinity,
b: (38..toString() / true, -0 || this) % ((a && (a.foo |= 4 + Infinity)) * (Infinity ^ {})),
NaN: b = a
}[--b + (typeof a == "function" && --_calls_ >= 0 && a(a++ + --b, (/[abc4]/.test(((--b + ((c = 1 + c,
("number" << "bar" > (-0 == "a")) >> (c = c + 1, 25) / (4 & "foo")) || a || 3).toString() || a || 3).toString() || b || 5).toString()) || 2).toString()[a && a.Infinity]))] ].null);
console.log(null, a, b, c, Infinity, NaN, undefined);
// uglified code
// (beautified)
var _calls_ = 10, b = 10, c = 0;
function f0(Math_2) {
var brake1 = 5;
do {} while (a && 0 < --brake1);
var expr3 = (c += 1) + ("function" == typeof f2 && 0 <= --_calls_ && f2(--b + (Infinity_1 && Infinity_1[b = a]), 22, (c += 1) + {
3: void 0,
foo: void ("function" == typeof Math_2 && 0 <= --_calls_ && Math_2()),
in: "boolean" != typeof b,
NaN: --b + ((c += 1) + -((null / 23..toString() & this - "") < 0) || a || 3).toString()
}["function" == typeof f3 && 0 <= --_calls_ && f3(-0, a++ + +function() {
c = 1 + c, c += 1, 24..toString();
for (var arguments_1 = (c = 1 + c, 0), brake7 = 5; c = 1 + (c = 1 + c), !0 !== (arguments_1 && (arguments_1[c = 1 + c,
(arguments_1 -= (23..toString() < 0) >> 0) && -this] >>= !0)) == (4 <= (2 === [ , 0 ].length) || !1) && 0 < --brake7; ) {}
for (var brake9 = 5; c = 1 + c, 0 < brake9; --brake9) {
c = 1 + c, arguments_1 && (arguments_1[c = 1 + c, !1 < 38..toString() >> null ^ 0] += !1);
}
}(), 2)]));
for (var key3 in expr3) {
c = 1 + c;
var Infinity_1 = expr3[key3];
return --b + function() {
for (var brake12 = 5; --b + Infinity_1 && 0 < --brake12; ) {
--b, a++, ++a;
}
}();
}
}
var a = f0(!1 === ((a = 100) && (a[c = 1 + c, !1 ^ (a && (a[c = 1 + c, a /= ([] - 22) % -2] = /[a2][^e]+$/ <= {})) / (a += !1)] = -.5)) == "foo" < {}, a++ + [ (c += 1) + -b, {
b: !function() {
var brake14 = 5;
do {
var b = (c += 1) + ((0 | (a && (a[c = 1 + c, NaN * (-0 < 24..toString()) % (22 ^ 23..toString() | !1)] += 23..toString() < -5))) ^ (a && (a[a++ + a] -= NaN)));
} while (--b + {
var: (c = 1 + c, NaN >>> (a += !1)),
b: (c = 1 + (c += 1), !0 === ("a" * 38..toString() ^ 0))
}.undefined && 0 < --brake14);
}(),
foo: --b,
3: 1 / 0,
b: (38..toString(), this % ((a && (a.foo |= 1 / 0)) * (1 / 0 ^ {}))),
NaN: b = a
}[--b + ("function" == typeof a && 0 <= --_calls_ && a(a++ + --b, (/[abc4]/.test(((--b + (c = 1 + c,
c += 1, (a || 3).toString()) || a || 3).toString() || b || 5).toString()) || 2).toString()[a && a.Infinity]))] ].null);
console.log(null, a, b, c, 1 / 0, NaN, void 0);
md5-dfdc55335159755b549feb2a34a99374
original result:
null undefined Infinity 9 Infinity NaN undefined
uglified result:
null undefined -Infinity 9 Infinity NaN undefined
md5-031184061b01aa6812723426e66c4203
minify(options):
{
"mangle": false
}
Suspicious compress options:
evaluate
```
Hard to improve on that reduced test case.
I spoke too soon. The following patch makes the xor bug test suite 8% faster and produces 40% smaller test cases on average - with the majority now being reduced to one-liners:
--- a/test/reduce.js
+++ b/test/reduce.js
@@ -110,6 +110,22 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
return expr;
}
}
+ else if (node instanceof U.AST_Assign) {
+ node.start._permute++;
+ CHANGED = true;
+ return node.left instanceof U.AST_Sub ? node.left : new U.AST_Call({
+ expression: new U.AST_Dot({
+ expression: new U.AST_SymbolRef({
+ name: "console",
+ start: {},
+ }),
+ property: "log",
+ start: {},
+ }),
+ args: [node.right],
+ start: {},
+ });
+ }
else if (node instanceof U.AST_Binary) {
CHANGED = true;
return [
With the patch the original code for this issue is also reduced to one line:
$ node-v10.4.1 bin/uglifyjs 3738-original.js --toplevel -mc --reduce-test
// Node.js v10.4.1 on darwin x64
// reduce test pass 1, iteration 0: 3809 bytes
// reduce test pass 1, iteration 25: 2283 bytes
// reduce test pass 1, iteration 50: 253 bytes
// reduce test pass 1, iteration 75: 154 bytes
// reduce test pass 1, iteration 100: 142 bytes
// reduce test pass 1, iteration 125: 140 bytes
// reduce test pass 1: 84 bytes
// reduce test pass 2: 24 bytes
// reduce test pass 3: 24 bytes
console.log(0 + ([] - 1) % 1);
// output: 0
//
// minify: -0
//
// options: {
// "compress": true,
// "mangle": true,
// "toplevel": true
// }
The patch was tested with toplevel mangle with no ill effects caused by the nodes introduced to create the console.log(rhs expression). But just to be on the safe side, please bash on the patch with mangle or rename related bugs.
Edit: updated patch - forgot the else.
Ignore the patch. If the assignment expression is in a loop then there could be multiple lines of output in the reduced test - even empty console.log()s are possible. In the worst case with nested loops there could be hundreds of lines of output.
It just occurred to me that the very rare hypothetical worst case scenario is easily remedied by constraining the unminified result to have 10 or fewer lines:
--- a/test/reduce.js
+++ b/test/reduce.js
@@ -112,2 +112,19 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
}
+ else if (node instanceof U.AST_Assign) {
+ node.start._permute++;
+ CHANGED = true;
+ if (node.left instanceof U.AST_Sub) return node.left;
+ return new U.AST_Call({
+ expression: new U.AST_Dot({
+ expression: new U.AST_SymbolRef({
+ name: "console",
+ start: {},
+ }),
+ property: "log",
+ start: {},
+ }),
+ args: [node.right],
+ start: {},
+ });
+ }
else if (node instanceof U.AST_Binary) {
@@ -548,2 +565,3 @@ function producesDifferentResultWhenMinified(result_cache, code, minify_options,
var unminified_result = run_code(result_cache, code, toplevel, max_timeout);
+ if (typeof unminified_result == "string" && !/^(.*\n){0,10}[^\n]*$/.test(unminified_result)) return false;
elapsed = Date.now() - elapsed;
Most helpful comment
Hard to improve on that reduced test case.