From 2b6657e96749989b55f0d98b00683016a1fccea1 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 15 Jan 2018 04:56:06 +0800 Subject: [PATCH 01/28] run `test/ufuzz.js` when Travis CI is idle (#2784) --- test/travis-ufuzz.js | 45 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 test/travis-ufuzz.js diff --git a/test/travis-ufuzz.js b/test/travis-ufuzz.js new file mode 100644 index 00000000..2fbe3b00 --- /dev/null +++ b/test/travis-ufuzz.js @@ -0,0 +1,45 @@ +"use strict"; + +var period = 20 * 60 * 1000; +if (process.argv.length > 2) { + var token = process.argv[2]; + var branch = process.argv[3] || "v" + require("../package.json").version; + (function init() { + setTimeout(init, period); + var options = require("url").parse("https://api.travis-ci.org/repo/mishoo%2FUglifyJS2/requests"); + options.method = "POST"; + options.headers = { + "Content-Type": "application/json", + "Travis-API-Version": 3, + "Authorization": "token " + token + }; + require("https").request(options, function(res) { + console.log("HTTP", res.statusCode); + console.log(JSON.stringify(res.headers, null, 2)); + console.log(); + res.setEncoding("utf8"); + res.on("data", console.log); + }).on("error", console.error).end(JSON.stringify({ + request: { + message: "ufuzz testing (when idle)", + branch: branch, + config: { + merge_mode: "replace", + language: "node_js", + node_js: "9", + sudo: false, + script: "node test/travis-ufuzz" + } + } + })); + })(); +} else { + var child = require("child_process").spawn("node", [ "test/ufuzz" ], { + stdio: [ "ignore", "ignore", 1 ] + }); + var keepAlive = setInterval(console.log, 5 * 60 * 1000); + setTimeout(function() { + clearInterval(keepAlive); + child.kill(); + }, period); +} From f96929c0313529dbf323ba20f7dc1be0f0e4e3ba Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 15 Jan 2018 15:08:35 +0800 Subject: [PATCH 02/28] improve `test/travis-ufuzz.js` (#2786) - use more RAM - show progress in console - report failure as job status --- test/travis-ufuzz.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/test/travis-ufuzz.js b/test/travis-ufuzz.js index 2fbe3b00..0ed76823 100644 --- a/test/travis-ufuzz.js +++ b/test/travis-ufuzz.js @@ -34,10 +34,24 @@ if (process.argv.length > 2) { })); })(); } else { - var child = require("child_process").spawn("node", [ "test/ufuzz" ], { - stdio: [ "ignore", "ignore", 1 ] + var child = require("child_process").spawn("node", [ + "--max-old-space-size=2048", + "test/ufuzz" + ], { + stdio: [ "ignore", "pipe", "pipe" ] }); - var keepAlive = setInterval(console.log, 5 * 60 * 1000); + var line = ""; + child.stdout.on("data", function(data) { + line += data; + }); + child.stderr.on("data", function() { + process.exitCode = (process.exitCode || 0) + 1; + }).pipe(process.stdout); + var keepAlive = setInterval(function() { + var end = line.lastIndexOf("\r"); + console.log(line.slice(line.lastIndexOf("\r", end - 1) + 1, end)); + line = line.slice(end + 1); + }, 5 * 60 * 1000); setTimeout(function() { clearInterval(keepAlive); child.kill(); From cbbe6fad60dd073c63575f591d631efa94bbceaf Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 15 Jan 2018 16:42:15 +0800 Subject: [PATCH 03/28] avoid double counting within single-use functions (#2785) fixes #2783 --- lib/compress.js | 1 + test/compress/functions.js | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/lib/compress.js b/lib/compress.js index 321a1340..7e3503fe 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -5022,6 +5022,7 @@ merge(Compressor.prototype, { } if (single_use && fixed) { if (fixed instanceof AST_Defun) { + fixed._squeezed = true; fixed = make_node(AST_Function, fixed, fixed); } var value; diff --git a/test/compress/functions.js b/test/compress/functions.js index 222aa0ca..2d55dd52 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -1951,3 +1951,41 @@ issue_2737_2: { } expect_stdout: "PASS" } + +issue_2783: { + options = { + collapse_vars: true, + conditionals: true, + if_return: true, + inline: true, + reduce_vars: true, + unused: true, + } + input: { + (function() { + return g; + function f(a) { + var b = a.b; + if (b) return b; + return a; + } + function g(o, i) { + while (i--) { + console.log(f(o)); + } + } + })()({ b: "PASS" }, 1); + } + expect: { + (function() { + return function(o,i) { + while (i--) console.log(f(o)); + }; + function f(a) { + var b = a.b; + return b || a; + } + })()({ b: "PASS" },1); + } + expect_stdout: "PASS" +} From b483678ca7f5e539cef9c57324ecc92d65800f7d Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 15 Jan 2018 16:42:31 +0800 Subject: [PATCH 04/28] avoid suboptimal termination in `passes` (#2787) --- lib/compress.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 7e3503fe..a461cb06 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -171,7 +171,8 @@ merge(Compressor.prototype, { node.process_expression(true); } var passes = +this.options.passes || 1; - var last_count = 1 / 0; + var min_count = 1 / 0; + var stopping = false; var mangle = { ie8: this.option("ie8") }; for (var pass = 0; pass < passes; pass++) { node.figure_out_scope(mangle); @@ -183,9 +184,15 @@ merge(Compressor.prototype, { node.walk(new TreeWalker(function() { count++; })); - this.info("pass " + pass + ": last_count: " + last_count + ", count: " + count); - if (count >= last_count) break; - last_count = count; + this.info("pass " + pass + ": last_count: " + min_count + ", count: " + count); + if (count < min_count) { + min_count = count; + stopping = false; + } else if (stopping) { + break; + } else { + stopping = true; + } } } if (this.option("expression")) { From 10f961c27b0db61c3a197f8f88080d9197361ea4 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 15 Jan 2018 18:47:23 +0800 Subject: [PATCH 05/28] enhance `collapse_vars` (#2788) --- lib/compress.js | 14 ++++++++++ test/compress/collapse_vars.js | 50 ++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/lib/compress.js b/lib/compress.js index a461cb06..fb1c2fdb 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1181,14 +1181,26 @@ merge(Compressor.prototype, { expr.definitions.forEach(extract_candidates); } else if (expr instanceof AST_DWLoop) { extract_candidates(expr.condition); + if (!(expr.body instanceof AST_Block)) { + extract_candidates(expr.body); + } } else if (expr instanceof AST_Exit) { if (expr.value) extract_candidates(expr.value); } else if (expr instanceof AST_For) { if (expr.init) extract_candidates(expr.init); if (expr.condition) extract_candidates(expr.condition); if (expr.step) extract_candidates(expr.step); + if (!(expr.body instanceof AST_Block)) { + extract_candidates(expr.body); + } } else if (expr instanceof AST_If) { extract_candidates(expr.condition); + if (!(expr.body instanceof AST_Block)) { + extract_candidates(expr.body); + } + if (expr.alternative && !(expr.alternative instanceof AST_Block)) { + extract_candidates(expr.alternative); + } } else if (expr instanceof AST_Sequence) { expr.expressions.forEach(extract_candidates); } else if (expr instanceof AST_SimpleStatement) { @@ -1215,10 +1227,12 @@ merge(Compressor.prototype, { if (parent instanceof AST_Call) return node; if (parent instanceof AST_Case) return node; if (parent instanceof AST_Conditional) return node; + if (parent instanceof AST_Definitions) return find_stop(parent, level + 1); if (parent instanceof AST_Exit) return node; if (parent instanceof AST_If) return node; if (parent instanceof AST_IterationStatement) return node; if (parent instanceof AST_Sequence) return find_stop(parent, level + 1); + if (parent instanceof AST_SimpleStatement) return find_stop(parent, level + 1); if (parent instanceof AST_Switch) return node; if (parent instanceof AST_VarDef) return node; return null; diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 12b49230..f252a7f4 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -4012,3 +4012,53 @@ replace_all_var: { } expect_stdout: "PASS" } + +cascade_statement: { + options = { + collapse_vars: true, + } + input: { + function f1(a, b) { + var c; + if (a) + return c = b, c || a; + else + c = a, c(b); + } + function f2(a, b) { + var c; + while (a) + c = b, a = c + b; + do + throw c = a + b, c; + while (c); + } + function f3(a, b) { + for (; a < b; a++) + if (c = a, c && b) + var c = (c = b(a), c); + } + } + expect: { + function f1(a, b) { + var c; + if (a) + return (c = b) || a; + else + (c = a)(b); + } + function f2(a, b) { + var c; + while (a) + a = (c = b) + b; + do + throw c = a + b; + while (c); + } + function f3(a, b) { + for (; a < b; a++) + if ((c = a) && b) + var c = c = b(a); + } + } +} From 7def684730ad3a684e64963961f3b33b308fd95d Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 15 Jan 2018 19:18:21 +0800 Subject: [PATCH 06/28] improve `test/travis-ufuzz.js` (#2789) - wait for instance to boot - run on forked repositories - workaround `request_limit_reached` --- test/travis-ufuzz.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/travis-ufuzz.js b/test/travis-ufuzz.js index 0ed76823..8685732f 100644 --- a/test/travis-ufuzz.js +++ b/test/travis-ufuzz.js @@ -1,12 +1,15 @@ "use strict"; -var period = 20 * 60 * 1000; +var period = 45 * 60 * 1000; +var wait = 2 * 60 * 1000; +var ping = 5 * 60 * 1000; if (process.argv.length > 2) { var token = process.argv[2]; var branch = process.argv[3] || "v" + require("../package.json").version; + var project = encodeURIComponent(process.argv[4] || "mishoo/UglifyJS2"); (function init() { - setTimeout(init, period); - var options = require("url").parse("https://api.travis-ci.org/repo/mishoo%2FUglifyJS2/requests"); + setTimeout(init, period + wait); + var options = require("url").parse("https://api.travis-ci.org/repo/" + project + "/requests"); options.method = "POST"; options.headers = { "Content-Type": "application/json", @@ -51,7 +54,7 @@ if (process.argv.length > 2) { var end = line.lastIndexOf("\r"); console.log(line.slice(line.lastIndexOf("\r", end - 1) + 1, end)); line = line.slice(end + 1); - }, 5 * 60 * 1000); + }, ping); setTimeout(function() { clearInterval(keepAlive); child.kill(); From ec7cd1dcf7c7a3b86288061b61ad72ba081f83bb Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 15 Jan 2018 23:41:39 +0800 Subject: [PATCH 07/28] handle VM failure gracefully (#2791) --- test/travis-ufuzz.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/travis-ufuzz.js b/test/travis-ufuzz.js index 8685732f..579b7448 100644 --- a/test/travis-ufuzz.js +++ b/test/travis-ufuzz.js @@ -42,21 +42,26 @@ if (process.argv.length > 2) { "test/ufuzz" ], { stdio: [ "ignore", "pipe", "pipe" ] + }).on("exit", function() { + console.log(line); + clearInterval(keepAlive); + clearTimeout(timer); }); var line = ""; child.stdout.on("data", function(data) { line += data; }); child.stderr.on("data", function() { - process.exitCode = (process.exitCode || 0) + 1; + process.exitCode = 1; }).pipe(process.stdout); var keepAlive = setInterval(function() { var end = line.lastIndexOf("\r"); console.log(line.slice(line.lastIndexOf("\r", end - 1) + 1, end)); line = line.slice(end + 1); }, ping); - setTimeout(function() { + var timer = setTimeout(function() { clearInterval(keepAlive); + child.removeAllListeners("exit"); child.kill(); }, period); } From 424173d311cd9c35a10a276abdab1de902158ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl=20Galeran?= Date: Tue, 16 Jan 2018 03:29:38 +0100 Subject: [PATCH 08/28] fix typo in README (#2792) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d7c47d72..df90d1ca 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ a double dash to prevent input files being used as option arguments: `debug` Add debug prefix and suffix. `domprops` Mangle property names that overlaps with DOM properties. - `keep_quoted` Only mangle unquoted properies. + `keep_quoted` Only mangle unquoted properties. `regex` Only mangle matched property names. `reserved` List of names that should not be mangled. -b, --beautify [options] Beautify output/specify output options: From b4aef753e7f65c0919c6c40b2b28d9f149bc81ed Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 16 Jan 2018 17:03:12 +0800 Subject: [PATCH 09/28] general improvements around `AST_ForIn` (#2796) - compress using `collapse_vars` - remove unused `name` - simplify `loop_body` --- lib/ast.js | 3 +-- lib/compress.js | 10 +++++++--- lib/parse.js | 2 -- test/compress/collapse_vars.js | 27 +++++++++++++++++++++++++++ 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index 65918675..4e41659c 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -267,11 +267,10 @@ var AST_For = DEFNODE("For", "init condition step", { } }, AST_IterationStatement); -var AST_ForIn = DEFNODE("ForIn", "init name object", { +var AST_ForIn = DEFNODE("ForIn", "init object", { $documentation: "A `for ... in` statement", $propdoc: { init: "[AST_Node] the `for/in` initialization code", - name: "[AST_SymbolRef?] the loop variable, only if `init` is AST_Var", object: "[AST_Node] the object that we're looping through" }, _walk: function(visitor) { diff --git a/lib/compress.js b/lib/compress.js index fb1c2fdb..cd5651a2 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -847,9 +847,8 @@ merge(Compressor.prototype, { }; function loop_body(x) { - if (x instanceof AST_Switch) return x; - if (x instanceof AST_For || x instanceof AST_ForIn || x instanceof AST_DWLoop) { - return (x.body instanceof AST_BlockStatement ? x.body : x); + if (x instanceof AST_IterationStatement) { + return x.body instanceof AST_BlockStatement ? x.body : x; } return x; }; @@ -1193,6 +1192,11 @@ merge(Compressor.prototype, { if (!(expr.body instanceof AST_Block)) { extract_candidates(expr.body); } + } else if (expr instanceof AST_ForIn) { + extract_candidates(expr.object); + if (!(expr.body instanceof AST_Block)) { + extract_candidates(expr.body); + } } else if (expr instanceof AST_If) { extract_candidates(expr.condition); if (!(expr.body instanceof AST_Block)) { diff --git a/lib/parse.js b/lib/parse.js index 001587bc..eba833dc 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1054,12 +1054,10 @@ function parse($TEXT, options) { }; function for_in(init) { - var lhs = init instanceof AST_Var ? init.definitions[0].name : null; var obj = expression(true); expect(")"); return new AST_ForIn({ init : init, - name : lhs, object : obj, body : in_loop(statement) }); diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index f252a7f4..9597b67f 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -4062,3 +4062,30 @@ cascade_statement: { } } } + +cascade_forin: { + options = { + collapse_vars: true, + } + input: { + var a; + function f(b) { + return [ b, b, b ]; + } + for (var c in a = console, f(a)) + console.log(c); + } + expect: { + var a; + function f(b) { + return [ b, b, b ]; + } + for (var c in f(a = console)) + console.log(c); + } + expect_stdout: [ + "0", + "1", + "2", + ] +} From 7857354d85589aef285aac7c36c96fe4f7e2143e Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 16 Jan 2018 17:33:21 +0800 Subject: [PATCH 10/28] improve `test/travis-ufuzz.js` (#2795) - print usage - support concurrent jobs - improve instance utilisation - resume after V8 self-destruct --- test/travis-ufuzz.js | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/test/travis-ufuzz.js b/test/travis-ufuzz.js index 579b7448..f84ed684 100644 --- a/test/travis-ufuzz.js +++ b/test/travis-ufuzz.js @@ -1,22 +1,29 @@ "use strict"; +var child_process = require("child_process"); +var https = require("https"); +var url = require("url"); + var period = 45 * 60 * 1000; var wait = 2 * 60 * 1000; var ping = 5 * 60 * 1000; -if (process.argv.length > 2) { +if (process.argv[2] == "run") { + for (var i = 0; i < 2; i++) spawn(); +} else if (process.argv.length > 2) { var token = process.argv[2]; var branch = process.argv[3] || "v" + require("../package.json").version; - var project = encodeURIComponent(process.argv[4] || "mishoo/UglifyJS2"); - (function init() { - setTimeout(init, period + wait); - var options = require("url").parse("https://api.travis-ci.org/repo/" + project + "/requests"); + var repository = encodeURIComponent(process.argv[4] || "mishoo/UglifyJS2"); + var concurrency = process.argv[5] || 1; + (function request() { + setTimeout(request, (period + wait) / concurrency); + var options = url.parse("https://api.travis-ci.org/repo/" + repository + "/requests"); options.method = "POST"; options.headers = { "Content-Type": "application/json", "Travis-API-Version": 3, "Authorization": "token " + token }; - require("https").request(options, function(res) { + https.request(options, function(res) { console.log("HTTP", res.statusCode); console.log(JSON.stringify(res.headers, null, 2)); console.log(); @@ -31,22 +38,22 @@ if (process.argv.length > 2) { language: "node_js", node_js: "9", sudo: false, - script: "node test/travis-ufuzz" + script: "node test/travis-ufuzz run" } } })); })(); } else { - var child = require("child_process").spawn("node", [ + console.log("Usage: test/travis-ufuzz.js [branch] [repository] [concurrency]"); +} + +function spawn() { + var child = child_process.spawn("node", [ "--max-old-space-size=2048", "test/ufuzz" ], { stdio: [ "ignore", "pipe", "pipe" ] - }).on("exit", function() { - console.log(line); - clearInterval(keepAlive); - clearTimeout(timer); - }); + }).on("exit", respawn); var line = ""; child.stdout.on("data", function(data) { line += data; @@ -61,7 +68,14 @@ if (process.argv.length > 2) { }, ping); var timer = setTimeout(function() { clearInterval(keepAlive); - child.removeAllListeners("exit"); + child.removeListener("exit", respawn); child.kill(); }, period); + + function respawn() { + console.log(line); + clearInterval(keepAlive); + clearTimeout(timer); + spawn(); + } } From 224c14d49d0f007af641b8c7d358814634ea7c5f Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 16 Jan 2018 17:51:25 +0800 Subject: [PATCH 11/28] improve `mocha` tests (#2797) - workaround sporadic delays from Travis CI --- test/mocha.js | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/test/mocha.js b/test/mocha.js index 411f52c5..fb8c3841 100644 --- a/test/mocha.js +++ b/test/mocha.js @@ -1,29 +1,24 @@ -var Mocha = require('mocha'), - fs = require('fs'), - path = require('path'); +var fs = require("fs"); +var Mocha = require("mocha"); +var path = require("path"); -// Instantiate a Mocha instance. -var mocha = new Mocha({}); +// Instantiate a Mocha instance +var mocha = new Mocha({ + timeout: 5000 +}); +var testDir = __dirname + "/mocha/"; -var testDir = __dirname + '/mocha/'; - -// Add each .js file to the mocha instance -fs.readdirSync(testDir).filter(function(file){ - // Only keep the .js files - return file.substr(-3) === '.js'; - -}).forEach(function(file){ - mocha.addFile( - path.join(testDir, file) - ); +// Add each .js file to the Mocha instance +fs.readdirSync(testDir).filter(function(file) { + return /\.js$/.test(file); +}).forEach(function(file) { + mocha.addFile(path.join(testDir, file)); }); module.exports = function() { mocha.run(function(failures) { - if (failures !== 0) { - process.on('exit', function () { - process.exit(failures); - }); - } + if (failures) process.on("exit", function() { + process.exit(failures); + }); }); -}; \ No newline at end of file +}; From 79cfac77bdee04021db9f60111f55005919f7b67 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 17 Jan 2018 13:58:27 +0800 Subject: [PATCH 12/28] extend `join_vars` & `sequences` (#2798) --- lib/compress.js | 88 +++++++++++++++++++++---------------- test/compress/properties.js | 48 ++++++++++++++++++-- test/compress/sequences.js | 18 ++++++++ 3 files changed, 111 insertions(+), 43 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index cd5651a2..5fd49cf1 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1662,37 +1662,34 @@ merge(Compressor.prototype, { for (var i = 0; i < statements.length; i++) { var stat = statements[i]; if (prev) { - if (stat instanceof AST_For && !(stat.init instanceof AST_Definitions)) { - var abort = false; - prev.body.walk(new TreeWalker(function(node) { - if (abort || node instanceof AST_Scope) return true; - if (node instanceof AST_Binary && node.operator == "in") { - abort = true; - return true; - } - })); - if (!abort) { - if (stat.init) stat.init = cons_seq(stat.init); - else { - stat.init = prev.body; - n--; - CHANGED = true; + if (stat instanceof AST_Exit) { + stat.value = cons_seq(stat.value || make_node(AST_Undefined, stat).transform(compressor)); + } else if (stat instanceof AST_For) { + if (!(stat.init instanceof AST_Definitions)) { + var abort = false; + prev.body.walk(new TreeWalker(function(node) { + if (abort || node instanceof AST_Scope) return true; + if (node instanceof AST_Binary && node.operator == "in") { + abort = true; + return true; + } + })); + if (!abort) { + if (stat.init) stat.init = cons_seq(stat.init); + else { + stat.init = prev.body; + n--; + CHANGED = true; + } } } - } - else if (stat instanceof AST_If) { + } else if (stat instanceof AST_ForIn) { + stat.object = cons_seq(stat.object); + } else if (stat instanceof AST_If) { stat.condition = cons_seq(stat.condition); - } - else if (stat instanceof AST_With) { + } else if (stat instanceof AST_Switch) { stat.expression = cons_seq(stat.expression); - } - else if (stat instanceof AST_Exit && stat.value) { - stat.value = cons_seq(stat.value); - } - else if (stat instanceof AST_Exit) { - stat.value = cons_seq(make_node(AST_Undefined, stat).transform(compressor)); - } - else if (stat instanceof AST_Switch) { + } else if (stat instanceof AST_With) { stat.expression = cons_seq(stat.expression); } } @@ -1775,18 +1772,7 @@ merge(Compressor.prototype, { defs = stat; } } else if (stat instanceof AST_Exit) { - var exprs = join_object_assignments(prev, stat.value); - if (exprs) { - CHANGED = true; - if (exprs.length) { - stat.value = make_sequence(stat.value, exprs); - } else if (stat.value instanceof AST_Sequence) { - stat.value = stat.value.tail_node().left; - } else { - stat.value = stat.value.left; - } - } - statements[++j] = stat; + stat.value = extract_object_assignments(stat.value); } else if (stat instanceof AST_For) { var exprs = join_object_assignments(prev, stat.init); if (exprs) { @@ -1808,6 +1794,10 @@ merge(Compressor.prototype, { } else { statements[++j] = stat; } + } else if (stat instanceof AST_ForIn) { + stat.object = extract_object_assignments(stat.object); + } else if (stat instanceof AST_If) { + stat.condition = extract_object_assignments(stat.condition); } else if (stat instanceof AST_SimpleStatement) { var exprs = join_object_assignments(prev, stat.body); if (exprs) { @@ -1816,11 +1806,31 @@ merge(Compressor.prototype, { stat.body = make_sequence(stat.body, exprs); } statements[++j] = stat; + } else if (stat instanceof AST_Switch) { + stat.expression = extract_object_assignments(stat.expression); + } else if (stat instanceof AST_With) { + stat.expression = extract_object_assignments(stat.expression); } else { statements[++j] = stat; } } statements.length = j + 1; + + function extract_object_assignments(value) { + statements[++j] = stat; + var exprs = join_object_assignments(prev, value); + if (exprs) { + CHANGED = true; + if (exprs.length) { + return make_sequence(value, exprs); + } else if (value instanceof AST_Sequence) { + return value.tail_node().left; + } else { + return value.left; + } + } + return value; + } } } diff --git a/test/compress/properties.js b/test/compress/properties.js index 7df53d39..933774d5 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -1189,7 +1189,7 @@ join_object_assignments_3: { expect_stdout: "PASS" } -join_object_assignments_4: { +join_object_assignments_return_1: { options = { join_vars: true, } @@ -1213,7 +1213,7 @@ join_object_assignments_4: { expect_stdout: "foo" } -join_object_assignments_5: { +join_object_assignments_return_2: { options = { join_vars: true, } @@ -1239,7 +1239,7 @@ join_object_assignments_5: { expect_stdout: "bar" } -join_object_assignments_6: { +join_object_assignments_return_3: { options = { join_vars: true, } @@ -1271,7 +1271,7 @@ join_object_assignments_6: { ] } -join_object_assignments_7: { +join_object_assignments_for: { options = { join_vars: true, } @@ -1298,3 +1298,43 @@ join_object_assignments_7: { "3", ] } + +join_object_assignments_if: { + options = { + join_vars: true, + } + input: { + console.log(function() { + var o = {}; + if (o.a = "PASS") return o.a; + }()) + } + expect: { + console.log(function() { + var o = { a: "PASS" }; + if (o.a) return o.a; + }()); + } + expect_stdout: "PASS" +} + +join_object_assignments_forin: { + options = { + join_vars: true, + } + input: { + console.log(function() { + var o = {}; + for (var a in o.a = "PASS", o) + return o[a]; + }()) + } + expect: { + console.log(function() { + var o = { a: "PASS" }; + for (var a in o) + return o[a]; + }()); + } + expect_stdout: "PASS" +} diff --git a/test/compress/sequences.js b/test/compress/sequences.js index 3d12fb0b..2c900796 100644 --- a/test/compress/sequences.js +++ b/test/compress/sequences.js @@ -859,3 +859,21 @@ for_init_var: { } expect_stdout: "PASS" } + +forin: { + options = { + sequences: true, + } + input: { + var o = []; + o.push("PASS"); + for (var a in o) + console.log(o[a]); + } + expect: { + var o = []; + for (var a in o.push("PASS"), o) + console.log(o[a]); + } + expect_stdout: "PASS" +} From cff3bf49142e5d99ad07e182169a73f985b7b652 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 17 Jan 2018 15:12:22 +0800 Subject: [PATCH 13/28] configure `rename` with CLI (#2802) --- bin/uglifyjs | 8 ++++++-- test/input/rename/input.js | 6 ++++++ test/mocha/cli.js | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 test/input/rename/input.js diff --git a/bin/uglifyjs b/bin/uglifyjs index 9a3257e7..2ff1feb7 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -43,6 +43,7 @@ program.option("-d, --define [=value]", "Global definitions.", parse_js("d program.option("--ie8", "Support non-standard Internet Explorer 8."); program.option("--keep-fnames", "Do not mangle/drop function names. Useful for code relying on Function.prototype.name."); program.option("--name-cache ", "File to hold mangled name mappings."); +program.option("--rename", "Force symbol expansion."); program.option("--no-rename", "Disable symbol expansion."); program.option("--self", "Build UglifyJS as a library (implies --wrap UglifyJS)"); program.option("--source-map [options]", "Enable source map/specify source map options.", parse_source_map()); @@ -62,13 +63,11 @@ if (!program.output && program.sourceMap && program.sourceMap.url != "inline") { "compress", "ie8", "mangle", - "rename", "sourceMap", "toplevel", "wrap" ].forEach(function(name) { if (name in program) { - if (name == "rename" && program[name]) return; options[name] = program[name]; } }); @@ -121,6 +120,11 @@ if (program.parse) { fatal("ERROR: inline source map only works with built-in parser"); } } +if (~program.rawArgs.indexOf("--rename")) { + options.rename = true; +} else if (!program.rename) { + options.rename = false; +} var convert_path = function(name) { return name; }; diff --git a/test/input/rename/input.js b/test/input/rename/input.js new file mode 100644 index 00000000..ef6daed2 --- /dev/null +++ b/test/input/rename/input.js @@ -0,0 +1,6 @@ +function f(x) { + return g(x); + function g(x) { + return x; + } +} diff --git a/test/mocha/cli.js b/test/mocha/cli.js index 85b2e8c9..671d700e 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -650,4 +650,36 @@ describe("bin/uglifyjs", function () { done(); }); }); + it("Should work with explicit --rename", function(done) { + var command = uglifyjscmd + " test/input/rename/input.js --rename"; + exec(command, function(err, stdout, stderr) { + if (err) throw err; + assert.strictEqual(stdout, "function f(a){return b(a);function b(c){return c}}\n"); + done(); + }); + }); + it("Should work with explicit --no-rename", function(done) { + var command = uglifyjscmd + " test/input/rename/input.js -mc --no-rename"; + exec(command, function(err, stdout, stderr) { + if (err) throw err; + assert.strictEqual(stdout, "function f(n){return function(n){return n}(n)}\n"); + done(); + }); + }); + it("Should work with implicit --rename", function(done) { + var command = uglifyjscmd + " test/input/rename/input.js -mc"; + exec(command, function(err, stdout, stderr) { + if (err) throw err; + assert.strictEqual(stdout, "function f(n){return n}\n"); + done(); + }); + }); + it("Should work with implicit --no-rename", function(done) { + var command = uglifyjscmd + " test/input/rename/input.js -c"; + exec(command, function(err, stdout, stderr) { + if (err) throw err; + assert.strictEqual(stdout, "function f(x){return function(x){return x}(x)}\n"); + done(); + }); + }); }); From d3ce2bc9e73d9d98b34b261d282a18b9ce9d5880 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 17 Jan 2018 20:41:51 +0800 Subject: [PATCH 14/28] suppress `unsafe_proto` for LHS expressions (#2804) --- lib/compress.js | 2 +- test/compress/properties.js | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/compress.js b/lib/compress.js index 5fd49cf1..64178227 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -5644,6 +5644,7 @@ merge(Compressor.prototype, { if (def) { return def.optimize(compressor); } + if (is_lhs(self, compressor.parent())) return self; if (compressor.option("unsafe_proto") && self.expression instanceof AST_Dot && self.expression.property == "prototype") { @@ -5682,7 +5683,6 @@ merge(Compressor.prototype, { break; } } - if (is_lhs(self, compressor.parent())) return self; var sub = self.flatten_object(self.property, compressor); if (sub) return sub.optimize(compressor); var ev = self.evaluate(compressor); diff --git a/test/compress/properties.js b/test/compress/properties.js index 933774d5..af115ff1 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -583,6 +583,25 @@ native_prototype: { } } +native_prototype_lhs: { + options = { + unsafe_proto: true, + } + input: { + console.log(function() { + Function.prototype.bar = "PASS"; + return function() {}; + }().bar); + } + expect: { + console.log(function() { + Function.prototype.bar = "PASS"; + return function() {}; + }().bar); + } + expect_stdout: "PASS" +} + accessor_boolean: { input: { var a = 1; From 07e4b64f3a8439a9491cc7a277872e0a5d79a29b Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 17 Jan 2018 21:33:13 +0800 Subject: [PATCH 15/28] fix `AST_Scope.clone()` (#2803) fixes #2799 --- lib/ast.js | 7 +++++ lib/utils.js | 7 +++++ test/compress/reduce_vars.js | 58 ++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/lib/ast.js b/lib/ast.js index 4e41659c..19f6bfb5 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -308,6 +308,13 @@ var AST_Scope = DEFNODE("Scope", "variables functions uses_with uses_eval parent enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes", cname: "[integer/S] current index for mangling variables (used internally by the mangler)", }, + clone: function(deep) { + var node = this._clone(deep); + if (this.variables) node.variables = this.variables.clone(); + if (this.functions) node.functions = this.functions.clone(); + if (this.enclosed) node.enclosed = this.enclosed.slice(); + return node; + } }, AST_Block); var AST_Toplevel = DEFNODE("Toplevel", "globals", { diff --git a/lib/utils.js b/lib/utils.js index dab7f566..9121fa93 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -303,6 +303,13 @@ Dictionary.prototype = { ret.push(f(this._values[i], i.substr(1))); return ret; }, + clone: function() { + var ret = new Dictionary(); + for (var i in this._values) + ret._values[i] = this._values[i]; + ret._size = this._size; + return ret; + }, toObject: function() { return this._values } }; Dictionary.fromObject = function(obj) { diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index ec0471a8..33175d1b 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -5302,3 +5302,61 @@ issue_2774: { } expect_stdout: "undefined" } + +issue_2799_1: { + options = { + reduce_funcs: true, + reduce_vars: true, + unused: true, + } + input: { + console.log(function() { + return f; + function f(n) { + function g(i) { + return i && i + g(i - 1); + } + function h(j) { + return g(j); + } + return h(n); + } + }()(5)); + } + expect: { + console.log(function() { + return function(n) { + return function(j) { + return function g(i) { + return i && i + g(i - 1); + }(j); + }(n); + } + }()(5)); + } + expect_stdout: "15" +} + +issue_2799_2: { + options = { + reduce_vars: true, + unsafe_proto: true, + unused: true, + } + input: { + (function() { + function foo() { + Function.prototype.call.apply(console.log, [ null, "PASS" ]); + } + foo(); + })(); + } + expect: { + (function() { + (function() { + (function() {}).call.apply(console.log, [ null, "PASS" ]); + })(); + })(); + } + expect_stdout: "PASS" +} From cc07f3b806f2a6ea971f290bd44425493f5c2e3d Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 18 Jan 2018 02:57:33 +0800 Subject: [PATCH 16/28] faster output of comments (#2806) --- lib/output.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/output.js b/lib/output.js index 1b1ba2a4..805f0339 100644 --- a/lib/output.js +++ b/lib/output.js @@ -451,6 +451,11 @@ function OutputStream(options) { return OUTPUT; }; + function has_nlb() { + var index = OUTPUT.lastIndexOf("\n"); + return /^ *$/.test(OUTPUT.slice(index + 1)); + } + function prepend_comments(node) { var self = this; var start = node.start; @@ -499,7 +504,7 @@ function OutputStream(options) { comments = comments.filter(comment_filter, node); if (comments.length == 0) return; - var last_nlb = /(^|\n) *$/.test(OUTPUT); + var last_nlb = has_nlb(); comments.forEach(function(c, i) { if (!last_nlb) { if (c.nlb) { @@ -546,7 +551,7 @@ function OutputStream(options) { print("\n"); indent(); need_newline_indented = false; - } else if (c.nlb && (i > 0 || !/(^|\n) *$/.test(OUTPUT))) { + } else if (c.nlb && (i > 0 || !has_nlb())) { print("\n"); indent(); } else if (i > 0 || !tail) { From b335912e8690469044b89ccf93ac9dec11b273f7 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 18 Jan 2018 14:08:05 +0800 Subject: [PATCH 17/28] enhance `test/ufuzz.js` (#2808) - standalone test for `rename` - handle `keep_fargs` & `rename` upon failure --- test/ufuzz.js | 24 ++++++++++++++++++++++-- test/ufuzz.json | 3 ++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/test/ufuzz.js b/test/ufuzz.js index d02e9f76..07b67a87 100644 --- a/test/ufuzz.js +++ b/test/ufuzz.js @@ -998,10 +998,11 @@ function log_suspects(minify_options, component) { if (typeof options != "object") options = {}; var defs = default_options[component]; var suspects = Object.keys(defs).filter(function(name) { - if ((name in options ? options : defs)[name]) { + var flip = name == "keep_fargs"; + if (flip ? name in options : (name in options ? options : defs)[name]) { var m = JSON.parse(JSON.stringify(minify_options)); var o = JSON.parse(JSON.stringify(options)); - o[name] = false; + o[name] = flip; m[component] = o; var result = UglifyJS.minify(original_code, m); if (result.error) { @@ -1022,6 +1023,24 @@ function log_suspects(minify_options, component) { } } +function log_rename(options) { + if (!options.rename) return; + var m = JSON.parse(JSON.stringify(minify_options)); + m.rename = false; + var result = UglifyJS.minify(original_code, m); + if (result.error) { + errorln("Error testing options.rename"); + errorln(result.error.stack); + } else { + var r = sandbox.run_code(result.code); + if (sandbox.same_stdout(original_result, r)) { + errorln("Suspicious options:"); + errorln(" rename"); + errorln(); + } + } +} + function log(options) { if (!ok) errorln('\n\n\n\n\n\n!!!!!!!!!!\n\n\n'); errorln("//============================================================="); @@ -1056,6 +1075,7 @@ function log(options) { errorln(); if (!ok && typeof uglify_code == "string") { Object.keys(default_options).forEach(log_suspects.bind(null, options)); + log_rename(options); errorln("!!!!!! Failed... round " + round); } } diff --git a/test/ufuzz.json b/test/ufuzz.json index 4057a351..f04b6417 100644 --- a/test/ufuzz.json +++ b/test/ufuzz.json @@ -5,7 +5,8 @@ "output": { "beautify": true, "bracketize": true - } + }, + "rename": true }, { "compress": false From 983e69128b0e6da78d71ad9b77d798f31a10ca44 Mon Sep 17 00:00:00 2001 From: kzc Date: Thu, 18 Jan 2018 08:52:54 -0500 Subject: [PATCH 18/28] fix `join_vars` property assignment for negative array index (#2810) fixes #2790 --- lib/compress.js | 2 +- test/compress/properties.js | 204 ++++++++++++++++++++++++++++++++++++ 2 files changed, 205 insertions(+), 1 deletion(-) diff --git a/lib/compress.js b/lib/compress.js index 64178227..89056796 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1746,7 +1746,7 @@ merge(Compressor.prototype, { } if (prop instanceof AST_Node) break; def.value.properties.push(make_node(AST_ObjectKeyVal, node, { - key: prop, + key: "" + prop, value: node.right })); exprs.shift(); diff --git a/test/compress/properties.js b/test/compress/properties.js index af115ff1..53684daa 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -1357,3 +1357,207 @@ join_object_assignments_forin: { } expect_stdout: "PASS" } + +join_object_assignments_negative: { + options = { + evaluate: true, + join_vars: true, + properties: true, + } + input: { + var o = {}; + o[0] = 0; + o[-0] = 1; + o[-1] = 2; + console.log(o[0], o[-0], o[-1]); + } + expect: { + var o = { + 0: 0, + 0: 1, + "-1": 2 + }; + console.log(o[0], o[-0], o[-1]); + } + expect_stdout: "1 1 2" +} + +join_object_assignments_NaN_1: { + options = { + join_vars: true, + } + input: { + var o = {}; + o[NaN] = 1; + o[0/0] = 2; + console.log(o[NaN], o[NaN]); + } + expect: { + var o = {}; + o[NaN] = 1; + o[0/0] = 2; + console.log(o[NaN], o[NaN]); + } + expect_stdout: "2 2" +} + +join_object_assignments_NaN_2: { + options = { + evaluate: true, + join_vars: true, + properties: true, + } + input: { + var o = {}; + o[NaN] = 1; + o[0/0] = 2; + console.log(o[NaN], o[NaN]); + } + expect: { + var o = { + NaN: 1, + NaN: 2 + }; + console.log(o.NaN, o.NaN); + } + expect_stdout: "2 2" +} + +join_object_assignments_null_0: { + options = { + join_vars: true, + } + input: { + var o = {}; + o[null] = 1; + console.log(o[null]); + } + expect: { + var o = {}; + o[null] = 1; + console.log(o[null]); + } + expect_stdout: "1" +} + +join_object_assignments_null_1: { + options = { + evaluate: true, + join_vars: true, + properties: true, + } + input: { + var o = {}; + o[null] = 1; + console.log(o[null]); + } + expect: { + var o = { + null: 1 + }; + console.log(o.null); + } + expect_stdout: "1" +} + +join_object_assignments_void_0: { + options = { + evaluate: true, + join_vars: true, + } + input: { + var o = {}; + o[void 0] = 1; + console.log(o[void 0]); + } + expect: { + var o = { + undefined: 1 + }; + console.log(o[void 0]); + } + expect_stdout: "1" +} + +join_object_assignments_undefined_1: { + options = { + join_vars: true, + } + input: { + var o = {}; + o[undefined] = 1; + console.log(o[undefined]); + } + expect: { + var o = {}; + o[void 0] = 1; + console.log(o[void 0]); + } + expect_stdout: "1" +} + +join_object_assignments_undefined_2: { + options = { + evaluate: true, + join_vars: true, + properties: true, + } + input: { + var o = {}; + o[undefined] = 1; + console.log(o[undefined]); + } + expect: { + var o = { + undefined : 1 + }; + console.log(o[void 0]); + } + expect_stdout: "1" +} + +join_object_assignments_Infinity: { + options = { + evaluate: true, + join_vars: true, + properties: true, + } + input: { + var o = {}; + o[Infinity] = 1; + o[1/0] = 2; + o[-Infinity] = 3; + o[-1/0] = 4; + console.log(o[Infinity], o[1/0], o[-Infinity], o[-1/0]); + } + expect: { + var o = { + Infinity: 1, + Infinity: 2, + "-Infinity": 3, + "-Infinity": 4 + }; + console.log(o[1/0], o[1/0], o[-1/0], o[-1/0]); + } + expect_stdout: "2 2 4 4" +} + +join_object_assignments_regex: { + options = { + evaluate: true, + join_vars: true, + properties: true, + } + input: { + var o = {}; + o[/rx/] = 1; + console.log(o[/rx/]); + } + expect: { + var o = { + "/rx/": 1 + }; + console.log(o[/rx/]); + } + expect_stdout: "1" +} From 082e004b872ecb158e5a28702898688742b5da86 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 19 Jan 2018 00:36:30 +0800 Subject: [PATCH 19/28] compress `undefined` property names (#2811) - enforce property names as string - handle `void 0` as `undefined` in `hoist_props` & `reduce_vars` --- lib/ast.js | 4 ++-- lib/compress.js | 21 ++++++++++++++++----- lib/output.js | 7 ++----- lib/parse.js | 4 ++-- test/compress/hoist_props.js | 22 ++++++++++++++++++++++ test/compress/issue-1770.js | 4 ++-- 6 files changed, 46 insertions(+), 16 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index 19f6bfb5..9b88b088 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -689,8 +689,8 @@ var AST_Object = DEFNODE("Object", "properties", { var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", { $documentation: "Base class for literal object properties", $propdoc: { - key: "[string] the property name converted to a string for ObjectKeyVal. For setters and getters this is an AST_SymbolAccessor.", - value: "[AST_Node] property value. For setters and getters this is an AST_Accessor." + key: "[string|AST_SymbolAccessor] property name. For ObjectKeyVal this is a string. For getters and setters this is an AST_SymbolAccessor.", + value: "[AST_Node] property value. For getters and setters this is an AST_Accessor." }, _walk: function(visitor) { return visitor._visit(this, function(){ diff --git a/lib/compress.js b/lib/compress.js index 89056796..bda5d7a6 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -404,14 +404,15 @@ merge(Compressor.prototype, { } function read_property(obj, key) { - if (key instanceof AST_Constant) key = key.getValue(); - if (key instanceof AST_Node) return null; + key = get_value(key); + if (key instanceof AST_Node) return; var value; if (obj instanceof AST_Array) { var elements = obj.elements; if (key == "length") return make_node_from_constant(elements.length, obj); if (typeof key == "number" && key in elements) value = elements[key]; } else if (obj instanceof AST_Object) { + key = "" + key; var props = obj.properties; for (var i = props.length; --i >= 0;) { var prop = props[i]; @@ -1855,6 +1856,18 @@ merge(Compressor.prototype, { })); }; + function get_value(key) { + if (key instanceof AST_Constant) { + return key.getValue(); + } + if (key instanceof AST_UnaryPrefix + && key.operator == "void" + && key.expression instanceof AST_Constant) { + return; + } + return key; + } + function is_undefined(node, compressor) { return node.is_undefined || node instanceof AST_Undefined @@ -3295,9 +3308,7 @@ merge(Compressor.prototype, { if (node instanceof AST_PropAccess && node.expression instanceof AST_SymbolRef) { var defs = defs_by_id[node.expression.definition().id]; if (defs) { - var key = node.property; - if (key instanceof AST_Node) key = key.getValue(); - var def = defs.get(key); + var def = defs.get(get_value(node.property)); var sym = make_node(AST_SymbolRef, node, { name: def.name, scope: node.expression.scope, diff --git a/lib/output.js b/lib/output.js index 805f0339..bd1a70c2 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1349,11 +1349,8 @@ function OutputStream(options) { function print_property_name(key, quote, output) { if (output.option("quote_keys")) { - output.print_string(key + ""); - } else if ((typeof key == "number" - || !output.option("beautify") - && +key + "" == key) - && parseFloat(key) >= 0) { + output.print_string(key); + } else if ("" + +key == key && key >= 0) { output.print(make_num(key)); } else if (RESERVED_WORDS(key) ? !output.option("ie8") : is_identifier_string(key)) { if (quote && output.option("keep_quoted_props")) { diff --git a/lib/parse.js b/lib/parse.js index eba833dc..af631149 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1365,7 +1365,7 @@ function parse($TEXT, options) { if (type == "name" && !is("punc", ":")) { var key = new AST_SymbolAccessor({ start: S.token, - name: as_property_name(), + name: "" + as_property_name(), end: prev() }); if (name == "get") { @@ -1391,7 +1391,7 @@ function parse($TEXT, options) { a.push(new AST_ObjectKeyVal({ start : start, quote : start.quote, - key : name, + key : "" + name, value : expression(false), end : prev() })); diff --git a/test/compress/hoist_props.js b/test/compress/hoist_props.js index 012a3fca..03867f78 100644 --- a/test/compress/hoist_props.js +++ b/test/compress/hoist_props.js @@ -664,3 +664,25 @@ issue_2519: { } expect_stdout: "5.5" } + +undefined_key: { + options = { + evaluate: true, + hoist_props: true, + join_vars: true, + passes: 4, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var a, o = {}; + o[a] = 1; + o.b = 2; + console.log(o[a] + o.b); + } + expect: { + console.log(3); + } + expect_stdout: "3" +} diff --git a/test/compress/issue-1770.js b/test/compress/issue-1770.js index f63f8453..7a529b2c 100644 --- a/test/compress/issue-1770.js +++ b/test/compress/issue-1770.js @@ -84,12 +84,12 @@ numeric_literal: { ' 0: 0,', ' "-0": 1,', ' 42: 2,', - ' "42": 3,', + ' 42: 3,', ' 37: 4,', ' o: 5,', ' 1e42: 6,', ' b: 7,', - ' "1e+42": 8', + ' 1e42: 8', '};', '', 'console.log(obj[-0], obj[-""], obj["-0"]);', From 81b64549ce1933974c904077eb3272ee0d4f4cf3 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 19 Jan 2018 06:11:19 +0800 Subject: [PATCH 20/28] fix time-out for respawned `test/ufuzz.js` (#2814) --- test/travis-ufuzz.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/travis-ufuzz.js b/test/travis-ufuzz.js index f84ed684..36d1fc18 100644 --- a/test/travis-ufuzz.js +++ b/test/travis-ufuzz.js @@ -8,7 +8,8 @@ var period = 45 * 60 * 1000; var wait = 2 * 60 * 1000; var ping = 5 * 60 * 1000; if (process.argv[2] == "run") { - for (var i = 0; i < 2; i++) spawn(); + var endTime = Date.now() + period; + for (var i = 0; i < 2; i++) spawn(endTime); } else if (process.argv.length > 2) { var token = process.argv[2]; var branch = process.argv[3] || "v" + require("../package.json").version; @@ -47,7 +48,7 @@ if (process.argv[2] == "run") { console.log("Usage: test/travis-ufuzz.js [branch] [repository] [concurrency]"); } -function spawn() { +function spawn(endTime) { var child = child_process.spawn("node", [ "--max-old-space-size=2048", "test/ufuzz" @@ -70,12 +71,12 @@ function spawn() { clearInterval(keepAlive); child.removeListener("exit", respawn); child.kill(); - }, period); + }, endTime - Date.now()); function respawn() { console.log(line); clearInterval(keepAlive); clearTimeout(timer); - spawn(); + spawn(endTime); } } From ac9a168fba726d9ab437478c81626f098997d116 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 19 Jan 2018 14:07:20 +0800 Subject: [PATCH 21/28] fix & improve `test/ufuzz.js` (#2815) - use correct `options` when testing `rename` - mask arbitrarily assigned function IDs to reduce rate of false positives --- test/sandbox.js | 24 +++++++++++++++--------- test/ufuzz.js | 2 +- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/test/sandbox.js b/test/sandbox.js index 2ce9f6e1..c660353c 100644 --- a/test/sandbox.js +++ b/test/sandbox.js @@ -17,24 +17,30 @@ function safe_log(arg, level) { return arg; } +function strip_func_ids(text) { + return text.toString().replace(/F[0-9]{6}N/g, "N>"); +} + var FUNC_TOSTRING = [ + "[ Array, Boolean, Error, Function, Number, Object, RegExp, String].forEach(function(f) {", + " f.toString = Function.prototype.toString;", + " f.valueOf = Function.prototype.valueOf;", + "});", "Function.prototype.toString = Function.prototype.valueOf = function() {", " var id = 100000;", " return function() {", - ' if (this === Array) return "[Function: Array]";', - ' if (this === Object) return "[Function: Object]";', - " var i = this.name;", - ' if (typeof i != "number") {', - " i = ++id;", + " var n = this.name;", + ' if (!/^F[0-9]{6}N$/.test(n)) {', + ' n = "F" + ++id + "N";', ].concat(Object.getOwnPropertyDescriptor(Function.prototype, "name").configurable ? [ ' Object.defineProperty(this, "name", {', " get: function() {", - " return i;", + " return n;", " }", " });", ] : [], [ " }", - ' return "[Function: " + i + "]";', + ' return "[Function: " + n + "]";', " }", "}();", 'Object.defineProperty(Function.prototype, "valueOf", { enumerable: false });', @@ -77,7 +83,7 @@ exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expec expected = expected.message.slice(expected.message.lastIndexOf("\n") + 1); actual = actual.message.slice(actual.message.lastIndexOf("\n") + 1); } - return expected == actual; + return strip_func_ids(expected) == strip_func_ids(actual); } : function(expected, actual) { - return typeof expected == typeof actual && expected.toString() == actual.toString(); + return typeof expected == typeof actual && strip_func_ids(expected) == strip_func_ids(actual); }; diff --git a/test/ufuzz.js b/test/ufuzz.js index 07b67a87..bbf4fa09 100644 --- a/test/ufuzz.js +++ b/test/ufuzz.js @@ -1025,7 +1025,7 @@ function log_suspects(minify_options, component) { function log_rename(options) { if (!options.rename) return; - var m = JSON.parse(JSON.stringify(minify_options)); + var m = JSON.parse(JSON.stringify(options)); m.rename = false; var result = UglifyJS.minify(original_code, m); if (result.error) { From e21bab7ce65e6a4db0ea966b3336eaab2a634e6e Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 19 Jan 2018 20:13:50 +0800 Subject: [PATCH 22/28] avoid duplicate property names in object literals under "use strict" (#2818) fixes #2816 --- lib/compress.js | 12 +++++++++--- test/compress/properties.js | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index bda5d7a6..00593e81 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -893,7 +893,7 @@ merge(Compressor.prototype, { sequencesize_2(statements, compressor); } if (compressor.option("join_vars")) { - join_consecutive_vars(statements, compressor); + join_consecutive_vars(statements); } if (compressor.option("collapse_vars")) { collapse(statements, compressor); @@ -1746,8 +1746,14 @@ merge(Compressor.prototype, { prop = prop.evaluate(compressor); } if (prop instanceof AST_Node) break; + prop = "" + prop; + if (compressor.has_directive("use strict")) { + if (!all(def.value.properties, function(node) { + return node.key != prop && node.key.name != prop; + })) break; + } def.value.properties.push(make_node(AST_ObjectKeyVal, node, { - key: "" + prop, + key: prop, value: node.right })); exprs.shift(); @@ -1756,7 +1762,7 @@ merge(Compressor.prototype, { return trimmed && exprs; } - function join_consecutive_vars(statements, compressor) { + function join_consecutive_vars(statements) { var defs; for (var i = 0, j = -1, len = statements.length; i < len; i++) { var stat = statements[i]; diff --git a/test/compress/properties.js b/test/compress/properties.js index 53684daa..dbc7bf2d 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -1561,3 +1561,30 @@ join_object_assignments_regex: { } expect_stdout: "1" } + +issue_2816: { + options = { + join_vars: true, + } + input: { + "use strict"; + var o = { + a: 1 + }; + o.b = 2; + o.a = 3; + o.c = 4; + console.log(o.a, o.b, o.c); + } + expect: { + "use strict"; + var o = { + a: 1, + b: 2 + }; + o.a = 3; + o.c = 4; + console.log(o.a, o.b, o.c); + } + expect_stdout: "3 2 4" +} From 3e7873217cad8b6f67839cb06d1e126ca231bc42 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 19 Jan 2018 20:41:57 +0800 Subject: [PATCH 23/28] improve `unused` on built-in functions (#2817) --- lib/compress.js | 216 +++++++++++++++++++++------------ test/compress/collapse_vars.js | 23 ++++ test/compress/dead-code.js | 17 +++ test/compress/evaluate.js | 36 ++++-- 4 files changed, 205 insertions(+), 87 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 00593e81..5235a05f 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -2113,6 +2113,95 @@ merge(Compressor.prototype, { return (first_in_statement(compressor) ? best_of_statement : best_of_expression)(ast1, ast2); } + function convert_to_predicate(obj) { + for (var key in obj) { + obj[key] = makePredicate(obj[key]); + } + } + + var object_fns = [ + "constructor", + "toString", + "valueOf", + ]; + var native_fns = { + Array: [ + "indexOf", + "join", + "lastIndexOf", + "slice", + ].concat(object_fns), + Boolean: object_fns, + Number: [ + "toExponential", + "toFixed", + "toPrecision", + ].concat(object_fns), + Object: object_fns, + RegExp: [ + "test", + ].concat(object_fns), + String: [ + "charAt", + "charCodeAt", + "concat", + "indexOf", + "italics", + "lastIndexOf", + "match", + "replace", + "search", + "slice", + "split", + "substr", + "substring", + "trim", + ].concat(object_fns), + }; + convert_to_predicate(native_fns); + var static_fns = { + Array: [ + "isArray", + ], + Math: [ + "abs", + "acos", + "asin", + "atan", + "ceil", + "cos", + "exp", + "floor", + "log", + "round", + "sin", + "sqrt", + "tan", + "atan2", + "pow", + "max", + "min", + ], + Number: [ + "isFinite", + "isNaN", + ], + Object: [ + "create", + "getOwnPropertyDescriptor", + "getOwnPropertyNames", + "getPrototypeOf", + "isExtensible", + "isFrozen", + "isSealed", + "keys", + ], + String: [ + "fromCharCode", + ], + }; + convert_to_predicate(static_fns); + // methods to evaluate a constant expression (function(def){ // If the node has been successfully reduced to a constant, @@ -2278,13 +2367,9 @@ merge(Compressor.prototype, { Array: Array, Math: Math, Number: Number, + Object: Object, String: String, }; - function convert_to_predicate(obj) { - for (var key in obj) { - obj[key] = makePredicate(obj[key]); - } - } var static_values = { Math: [ "E", @@ -2325,77 +2410,6 @@ merge(Compressor.prototype, { } return this; }); - var object_fns = [ - "constructor", - "toString", - "valueOf", - ]; - var native_fns = { - Array: [ - "indexOf", - "join", - "lastIndexOf", - "slice", - ].concat(object_fns), - Boolean: object_fns, - Number: [ - "toExponential", - "toFixed", - "toPrecision", - ].concat(object_fns), - RegExp: [ - "test", - ].concat(object_fns), - String: [ - "charAt", - "charCodeAt", - "concat", - "indexOf", - "italics", - "lastIndexOf", - "match", - "replace", - "search", - "slice", - "split", - "substr", - "substring", - "trim", - ].concat(object_fns), - }; - convert_to_predicate(native_fns); - var static_fns = { - Array: [ - "isArray", - ], - Math: [ - "abs", - "acos", - "asin", - "atan", - "ceil", - "cos", - "exp", - "floor", - "log", - "round", - "sin", - "sqrt", - "tan", - "atan2", - "pow", - "max", - "min" - ], - Number: [ - "isFinite", - "isNaN", - ], - String: [ - "fromCharCode", - ], - }; - convert_to_predicate(static_fns); def(AST_Call, function(compressor, depth) { var exp = this.expression; if (compressor.option("unsafe") && exp instanceof AST_PropAccess) { @@ -2420,7 +2434,16 @@ merge(Compressor.prototype, { if (arg === value) return this; args.push(value); } - return val[key].apply(val, args); + try { + return val[key].apply(val, args); + } catch (ex) { + compressor.warn("Error evaluating {code} [{file}:{line},{col}]", { + code: this.print_to_string(), + file: this.start.file, + line: this.start.line, + col: this.start.col + }); + } } return this; }); @@ -2511,9 +2534,34 @@ merge(Compressor.prototype, { if (compressor.option("unsafe")) { var expr = this.expression; if (is_undeclared_ref(expr) && global_pure_fns(expr.name)) return true; + if (expr instanceof AST_Dot + && is_undeclared_ref(expr.expression) + && (static_fns[expr.expression.name] || return_false)(expr.property)) { + return true; + } } return this.pure || !compressor.pure_funcs(this); }); + AST_Node.DEFMETHOD("is_call_pure", return_false); + AST_Dot.DEFMETHOD("is_call_pure", function(compressor) { + if (!compressor.option("unsafe")) return; + var expr = this.expression; + var fns = return_false; + if (expr instanceof AST_Array) { + fns = native_fns.Array; + } else if (expr.is_boolean()) { + fns = native_fns.Boolean; + } else if (expr.is_number(compressor)) { + fns = native_fns.Number; + } else if (expr instanceof AST_RegExp) { + fns = native_fns.RegExp; + } else if (expr.is_string(compressor)) { + fns = native_fns.String; + } else if (!this.may_throw_on_access(compressor)) { + fns = native_fns.Object; + } + return fns(this.property); + }); // determine if expression has side effects (function(def){ @@ -2534,8 +2582,12 @@ merge(Compressor.prototype, { return any(this.body, compressor); }); def(AST_Call, function(compressor){ - return !this.is_expr_pure(compressor) - || any(this.args, compressor); + if (!this.is_expr_pure(compressor) + && (!this.expression.is_call_pure(compressor) + || this.expression.has_side_effects(compressor))) { + return true; + } + return any(this.args, compressor); }); def(AST_Switch, function(compressor){ return this.expression.has_side_effects(compressor) @@ -3365,6 +3417,12 @@ merge(Compressor.prototype, { def(AST_This, return_null); def(AST_Call, function(compressor, first_in_statement){ if (!this.is_expr_pure(compressor)) { + if (this.expression.is_call_pure(compressor)) { + var exprs = this.args.slice(); + exprs.unshift(this.expression.expression); + exprs = trim(exprs, compressor, first_in_statement); + return exprs && make_sequence(this, exprs); + } if (this.expression instanceof AST_Function && (!this.expression.name || !this.expression.name.definition().references.length)) { var node = this.clone(); diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 9597b67f..970f822e 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -4089,3 +4089,26 @@ cascade_forin: { "2", ] } + +unsafe_builtin: { + options = { + collapse_vars: true, + pure_getters: "strict", + unsafe: true, + unused: true, + } + input: { + function f(a) { + var b = Math.abs(a); + return Math.pow(b, 2); + } + console.log(f(-1), f(2)); + } + expect: { + function f(a) { + return Math.pow(Math.abs(a), 2); + } + console.log(f(-1), f(2)); + } + expect_stdout: "1 4" +} diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index afc8c568..b66d5ac1 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -862,3 +862,20 @@ issue_2749: { } expect_stdout: "PASS" } + +unsafe_builtin: { + options = { + side_effects: true, + unsafe: true, + } + input: { + (!w).constructor(x); + Math.abs(y); + [ 1, 2, z ].valueOf(); + } + expect: { + w, x; + y; + z; + } +} diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 6106fce3..d40880f3 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -1194,6 +1194,9 @@ issue_2231_1: { console.log(Object.keys(void 0)); } expect_stdout: true + expect_warnings: [ + "WARN: Error evaluating Object.keys(void 0) [test/compress/evaluate.js:1191,20]", + ] } issue_2231_2: { @@ -1208,6 +1211,23 @@ issue_2231_2: { console.log(Object.getOwnPropertyNames(null)); } expect_stdout: true + expect_warnings: [ + "WARN: Error evaluating Object.getOwnPropertyNames(null) [test/compress/evaluate.js:1208,20]", + ] +} + +issue_2231_3: { + options = { + evaluate: true, + unsafe: true, + } + input: { + console.log(Object.keys({ foo: "bar" })[0]); + } + expect: { + console.log("foo"); + } + expect_stdout: "foo" } self_comparison_1: { @@ -1330,13 +1350,13 @@ issue_2535_3: { } expect_stdout: true expect_warnings: [ - "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1316,20]", - "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1317,20]", - "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1318,20]", - "WARN: Condition left of && always false [test/compress/evaluate.js:1318,20]", - "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1319,20]", - "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1320,20]", - "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1321,20]", - "WARN: Condition left of || always true [test/compress/evaluate.js:1321,20]", + "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1336,20]", + "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1337,20]", + "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1338,20]", + "WARN: Condition left of && always false [test/compress/evaluate.js:1338,20]", + "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1339,20]", + "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1340,20]", + "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1341,20]", + "WARN: Condition left of || always true [test/compress/evaluate.js:1341,20]", ] } From 069df27bf18247a3e2f1d1a4abd0ee4fe28ef0ce Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 19 Jan 2018 23:47:42 +0800 Subject: [PATCH 24/28] enable `unsafe` for `test/ufuzz.js` (#2819) - introduce `unsafe_undefined` - safer `.toString()` compression Miscellaneous - rename `unsafe_Function` --- README.md | 9 +++++---- lib/compress.js | 19 +++++++++++-------- test/compress/functions.js | 2 +- test/compress/issue-1443.js | 4 ++-- test/compress/issue-1588.js | 2 +- test/compress/sequences.js | 2 +- test/ufuzz.json | 8 +++++++- 7 files changed, 28 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index df90d1ca..99e33af7 100644 --- a/README.md +++ b/README.md @@ -737,7 +737,7 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u comparison are switching. Compression only works if both `comparisons` and `unsafe_comps` are both set to true. -- `unsafe_Func` (default: `false`) -- compress and mangle `Function(args, code)` +- `unsafe_Function` (default: `false`) -- compress and mangle `Function(args, code)` when both `args` and `code` are string literals. - `unsafe_math` (default: `false`) -- optimize numerical expressions like @@ -749,6 +749,10 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u - `unsafe_regexp` (default: `false`) -- enable substitutions of variables with `RegExp` values the same way as if they are constants. +- `unsafe_undefined` (default: `false`) -- substitute `void 0` if there is a + variable named `undefined` in scope (variable name will be mangled, typically + reduced to a single character) + - `unused` (default: `true`) -- drop unreferenced functions and variables (simple direct variable assignments do not count as references unless set to `"keep_assign"`) @@ -922,9 +926,6 @@ when this flag is on: - `new Object()` → `{}` - `String(exp)` or `exp.toString()` → `"" + exp` - `new Object/RegExp/Function/Error/Array (...)` → we discard the `new` -- `void 0` → `undefined` (if there is a variable named "undefined" in - scope; we do it because the variable name will be mangled, typically - reduced to a single character) ### Conditional compilation diff --git a/lib/compress.js b/lib/compress.js index 5235a05f..49185aec 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -84,10 +84,11 @@ function Compressor(options, false_by_default) { typeofs : !false_by_default, unsafe : false, unsafe_comps : false, - unsafe_Func : false, + unsafe_Function: false, unsafe_math : false, unsafe_proto : false, unsafe_regexp : false, + unsafe_undefined: false, unused : !false_by_default, warnings : false, }, true); @@ -4100,11 +4101,13 @@ merge(Compressor.prototype, { break; } else if (exp instanceof AST_Dot) switch(exp.property) { case "toString": - if (self.args.length == 0) return make_node(AST_Binary, self, { - left: make_node(AST_String, self, { value: "" }), - operator: "+", - right: exp.expression - }).optimize(compressor); + if (self.args.length == 0 && !exp.expression.may_throw_on_access(compressor)) { + return make_node(AST_Binary, self, { + left: make_node(AST_String, self, { value: "" }), + operator: "+", + right: exp.expression + }).optimize(compressor); + } break; case "join": if (exp.expression instanceof AST_Array) EXIT: { @@ -4212,7 +4215,7 @@ merge(Compressor.prototype, { break; } } - if (compressor.option("unsafe_Func") + if (compressor.option("unsafe_Function") && is_undeclared_ref(exp) && exp.name == "Function") { // new Function() => function(){} @@ -5220,7 +5223,7 @@ merge(Compressor.prototype, { } OPT(AST_Undefined, function(self, compressor){ - if (compressor.option("unsafe")) { + if (compressor.option("unsafe_undefined")) { var undef = find_variable(compressor, "undefined"); if (undef) { var ref = make_node(AST_SymbolRef, self, { diff --git a/test/compress/functions.js b/test/compress/functions.js index 2d55dd52..87328820 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -218,7 +218,7 @@ issue_203: { options = { keep_fargs: false, side_effects: true, - unsafe_Func: true, + unsafe_Function: true, unused: true, } input: { diff --git a/test/compress/issue-1443.js b/test/compress/issue-1443.js index 304a71ac..18554ff6 100644 --- a/test/compress/issue-1443.js +++ b/test/compress/issue-1443.js @@ -4,7 +4,7 @@ unsafe_undefined: { options = { conditionals: true, if_return: true, - unsafe: true + unsafe_undefined: true, } mangle = {} input: { @@ -30,7 +30,7 @@ keep_fnames: { options = { conditionals: true, if_return: true, - unsafe: true + unsafe_undefined: true, } mangle = { keep_fnames: true diff --git a/test/compress/issue-1588.js b/test/compress/issue-1588.js index 187d9f6c..40efb63b 100644 --- a/test/compress/issue-1588.js +++ b/test/compress/issue-1588.js @@ -61,7 +61,7 @@ unsafe_undefined: { options = { conditionals: true, if_return: true, - unsafe: true, + unsafe_undefined: true, } mangle = {} input: { diff --git a/test/compress/sequences.js b/test/compress/sequences.js index 2c900796..03075bf1 100644 --- a/test/compress/sequences.js +++ b/test/compress/sequences.js @@ -288,7 +288,7 @@ unsafe_undefined: { if_return: true, sequences: true, side_effects: true, - unsafe: true, + unsafe_undefined: true, } input: { function f(undefined) { diff --git a/test/ufuzz.json b/test/ufuzz.json index f04b6417..5ccd96e0 100644 --- a/test/ufuzz.json +++ b/test/ufuzz.json @@ -21,7 +21,13 @@ { "compress": { "keep_fargs": false, - "passes": 100 + "passes": 1e6, + "sequences": 1e6, + "unsafe": true, + "unsafe_Function": true, + "unsafe_math": true, + "unsafe_proto": true, + "unsafe_regexp": true } } ] From e2dc9cf091684638a75035e7acddbc32ac5c0785 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 21 Jan 2018 01:39:44 +0800 Subject: [PATCH 25/28] fix `unsafe` `evaluate` of `AST_Array` (#2825) fixes #2822 --- lib/compress.js | 5 ++++- test/compress/evaluate.js | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/compress.js b/lib/compress.js index 49185aec..2803b85c 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -2240,7 +2240,10 @@ merge(Compressor.prototype, { var elements = []; for (var i = 0, len = this.elements.length; i < len; i++) { var element = this.elements[i]; - if (element instanceof AST_Function) continue; + if (element instanceof AST_Function) { + elements.push(element); + continue; + } var value = element._eval(compressor, depth); if (element === value) return this; elements.push(value); diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index d40880f3..0d26982c 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -1360,3 +1360,17 @@ issue_2535_3: { "WARN: Condition left of || always true [test/compress/evaluate.js:1341,20]", ] } + +issue_2822: { + options = { + evaluate: true, + unsafe: true, + } + input: { + console.log([ function() {}, "PASS", "FAIL" ][1]); + } + expect: { + console.log("PASS"); + } + expect_stdout: "PASS" +} From 06166df999f71a033d52e1a6213c36dd5bace2e8 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 21 Jan 2018 07:08:01 +0000 Subject: [PATCH 26/28] v3.3.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index aaf6370b..a8ea8408 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "homepage": "http://lisperator.net/uglifyjs", "author": "Mihai Bazon (http://lisperator.net/)", "license": "BSD-2-Clause", - "version": "3.3.7", + "version": "3.3.8", "engines": { "node": ">=0.8.0" }, From 13accdd745caf857ad8877caf2a93594ddaeb769 Mon Sep 17 00:00:00 2001 From: alexlamsl Date: Sun, 21 Jan 2018 15:53:32 +0800 Subject: [PATCH 27/28] fix tests --- test/compress/evaluate.js | 20 ++++++++++---------- test/compress/harmony.js | 6 ++---- test/compress/issue-203.js | 6 +++--- test/mocha/cli.js | 32 -------------------------------- 4 files changed, 15 insertions(+), 49 deletions(-) diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 0230de10..7792dc94 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -1288,7 +1288,7 @@ issue_2231_1: { } expect_stdout: true expect_warnings: [ - "WARN: Error evaluating Object.keys(void 0) [test/compress/evaluate.js:1191,20]", + "WARN: Error evaluating Object.keys(void 0) [test/compress/evaluate.js:1284,20]", ] } @@ -1305,7 +1305,7 @@ issue_2231_2: { } expect_stdout: true expect_warnings: [ - "WARN: Error evaluating Object.getOwnPropertyNames(null) [test/compress/evaluate.js:1208,20]", + "WARN: Error evaluating Object.getOwnPropertyNames(null) [test/compress/evaluate.js:1301,20]", ] } @@ -1443,14 +1443,14 @@ issue_2535_3: { } expect_stdout: true expect_warnings: [ - "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1409,20]", - "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1410,20]", - "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1411,20]", - "WARN: Condition left of && always false [test/compress/evaluate.js:1411,20]", - "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1412,20]", - "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1413,20]", - "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1414,20]", - "WARN: Condition left of || always true [test/compress/evaluate.js:1414,20]", + "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1429,20]", + "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1430,20]", + "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1431,20]", + "WARN: Condition left of && always false [test/compress/evaluate.js:1431,20]", + "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1432,20]", + "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1433,20]", + "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1434,20]", + "WARN: Condition left of || always true [test/compress/evaluate.js:1434,20]", ] } diff --git a/test/compress/harmony.js b/test/compress/harmony.js index f8120eae..29a27b7d 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -1276,8 +1276,7 @@ issue_2794_1: { } expect: { function foo() { - for (const a of (va = value, doSomething(va))) console.log(a); - var va; + for (const a of doSomething(value)) console.log(a); } function doSomething(x) { return [ x, 2 * x, 3 * x ]; @@ -1326,8 +1325,7 @@ issue_2794_2: { } expect: { function foo() { - for (const n of (o = value, doSomething(o))) console.log(n); - var o; + for (const o of doSomething(value)) console.log(o); } function doSomething(o) { return [ o, 2 * o, 3 * o ]; diff --git a/test/compress/issue-203.js b/test/compress/issue-203.js index 896982b5..e95ca23e 100644 --- a/test/compress/issue-203.js +++ b/test/compress/issue-203.js @@ -2,7 +2,7 @@ compress_new_function: { options = { unsafe: true, - unsafe_Func: true, + unsafe_Function: true, } input: { new Function("aa, bb", 'return aa;'); @@ -15,7 +15,7 @@ compress_new_function: { compress_new_function_with_destruct: { options = { unsafe: true, - unsafe_Func: true, + unsafe_Function: true, ecma: 6 } beautify = { @@ -38,7 +38,7 @@ compress_new_function_with_destruct_arrows: { arrows: true, unsafe_arrows: true, unsafe: true, - unsafe_Func: true, + unsafe_Function: true, ecma: 6, } beautify = { diff --git a/test/mocha/cli.js b/test/mocha/cli.js index 33e111a2..3804d823 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -743,36 +743,4 @@ describe("bin/uglifyjs", function () { done(); }); }); - it("Should work with explicit --rename", function(done) { - var command = uglifyjscmd + " test/input/rename/input.js --rename"; - exec(command, function(err, stdout, stderr) { - if (err) throw err; - assert.strictEqual(stdout, "function f(a){return b(a);function b(c){return c}}\n"); - done(); - }); - }); - it("Should work with explicit --no-rename", function(done) { - var command = uglifyjscmd + " test/input/rename/input.js -mc --no-rename"; - exec(command, function(err, stdout, stderr) { - if (err) throw err; - assert.strictEqual(stdout, "function f(n){return function(n){return n}(n)}\n"); - done(); - }); - }); - it("Should work with implicit --rename", function(done) { - var command = uglifyjscmd + " test/input/rename/input.js -mc"; - exec(command, function(err, stdout, stderr) { - if (err) throw err; - assert.strictEqual(stdout, "function f(n){return n}\n"); - done(); - }); - }); - it("Should work with implicit --no-rename", function(done) { - var command = uglifyjscmd + " test/input/rename/input.js -c"; - exec(command, function(err, stdout, stderr) { - if (err) throw err; - assert.strictEqual(stdout, "function f(x){return function(x){return x}(x)}\n"); - done(); - }); - }); }); From fc98d212db69dde8ce15e2511984924ebf4b1e55 Mon Sep 17 00:00:00 2001 From: alexlamsl Date: Sun, 21 Jan 2018 15:58:14 +0800 Subject: [PATCH 28/28] allow duplicate property names in object literals for ES6+ --- lib/compress.js | 2 +- test/compress/properties.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/compress.js b/lib/compress.js index 2fd8c4fb..2db9e03a 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1865,7 +1865,7 @@ merge(Compressor.prototype, { } if (prop instanceof AST_Node) break; prop = "" + prop; - if (compressor.has_directive("use strict")) { + if (compressor.option("ecma") < 6 && compressor.has_directive("use strict")) { if (!all(def.value.properties, function(node) { return node.key != prop && node.key.name != prop; })) break; diff --git a/test/compress/properties.js b/test/compress/properties.js index bb94d4d2..515d3125 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -1865,3 +1865,32 @@ issue_2816: { } expect_stdout: "3 2 4" } + +issue_2816_ecma6: { + options = { + ecma: "6", + join_vars: true, + } + input: { + "use strict"; + var o = { + a: 1 + }; + o.b = 2; + o.a = 3; + o.c = 4; + console.log(o.a, o.b, o.c); + } + expect: { + "use strict"; + var o = { + a: 1, + b: 2, + a: 3, + c: 4 + }; + console.log(o.a, o.b, o.c); + } + expect_stdout: "3 2 4" + node_version: ">=4" +}