From 145874e50401137e573f180e1b36ccb5b547840b Mon Sep 17 00:00:00 2001 From: kzc Date: Sat, 8 Jul 2017 12:48:53 -0400 Subject: [PATCH 01/11] docs: update benchmarks using node 8, add babili (#2218) --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index fd142f4b..621c524a 100644 --- a/README.md +++ b/README.md @@ -1032,8 +1032,8 @@ in total it's a bit more than just using UglifyJS's own parser. ### Uglify Fast Minify Mode -It's not well known, but variable and function name mangling accounts for -95% of the size reduction in minified code for most javascript - not +It's not well known, but whitespace removal and symbol mangling accounts +for 95% of the size reduction in minified code for most javascript - not elaborate code transforms. One can simply disable `compress` to speed up Uglify builds by 3 to 4 times. In this fast `mangle`-only mode Uglify has comparable minify speeds and gzip sizes to @@ -1042,10 +1042,11 @@ comparable minify speeds and gzip sizes to | d3.js | minify size | gzip size | minify time (seconds) | | --- | ---: | ---: | ---: | | original | 451,131 | 108,733 | - | -| uglify-js@3.0.23 mangle=false, compress=false | 316,600 | 85,245 | 0.73 | -| uglify-js@3.0.23 mangle=true, compress=false | 220,216 | 72,730 | 1.21 | -| Butternut 0.4.6 | 217,568 | 72,738 | 1.81 | -| uglify-js@3.0.23 mangle=true, compress=true | 212,511 | 71,560 | 4.64 | +| uglify-js@3.0.24 mangle=false, compress=false | 316,600 | 85,245 | 0.70 | +| uglify-js@3.0.24 mangle=true, compress=false | 220,216 | 72,730 | 1.13 | +| butternut@0.4.6 | 217,568 | 72,738 | 1.41 | +| uglify-js@3.0.24 mangle=true, compress=true | 212,511 | 71,560 | 3.36 | +| babili@0.1.4 | 210,713 | 72,140 | 12.64 | To enable fast minify mode from the CLI use: ``` From 4956ad311b363374211b7767b58b80098bac1ee3 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 9 Jul 2017 01:44:59 +0800 Subject: [PATCH 02/11] benchmark gzipped output (#2220) --- test/benchmark.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/benchmark.js b/test/benchmark.js index e34f0858..569d4476 100644 --- a/test/benchmark.js +++ b/test/benchmark.js @@ -6,6 +6,7 @@ var createHash = require("crypto").createHash; var fetch = require("./fetch"); var fork = require("child_process").fork; +var zlib = require("zlib"); var args = process.argv.slice(2); if (!args.length) { args.push("-mc"); @@ -33,6 +34,7 @@ function done() { console.log(info.log); console.log("Original:", info.input, "bytes"); console.log("Uglified:", info.output, "bytes"); + console.log("GZipped: ", info.gzip, "bytes"); console.log("SHA1 sum:", info.sha1); if (info.code) { failures.push(url); @@ -51,6 +53,7 @@ urls.forEach(function(url) { results[url] = { input: 0, output: 0, + gzip: 0, log: "" }; fetch(url, function(err, res) { @@ -65,6 +68,11 @@ urls.forEach(function(url) { results[url].sha1 = data.toString("hex"); done(); }); + uglifyjs.stdout.pipe(zlib.createGzip({ + level: zlib.Z_BEST_COMPRESSION + })).on("data", function(data) { + results[url].gzip += data.length; + }); uglifyjs.stderr.setEncoding("utf8"); uglifyjs.stderr.on("data", function(data) { results[url].log += data; From 10a938cb79d19e1476e2744c8651011fd4d7b902 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 11 Jul 2017 02:34:28 +0800 Subject: [PATCH 03/11] enhance source mapping on IIFEs (#2224) fixes #2213 --- lib/output.js | 3 +++ test/mocha/cli.js | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/output.js b/lib/output.js index 9416583a..6ee96b31 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1104,6 +1104,9 @@ function OutputStream(options) { self.expression.print(output); if (self instanceof AST_New && !need_constructor_parens(self, output)) return; + if (self.expression instanceof AST_Lambda) { + output.add_mapping(self.start); + } output.with_parens(function(){ self.args.forEach(function(expr, i){ if (i) output.comma(); diff --git a/test/mocha/cli.js b/test/mocha/cli.js index 4c4943c6..8bfdc85a 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -63,7 +63,7 @@ describe("bin/uglifyjs", function () { if (err) throw err; assert.strictEqual(stdout, "var bar=function(){function foo(bar){return bar}return foo}();\n" + - "//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMTMyMy9zYW1wbGUuanMiXSwibmFtZXMiOlsiYmFyIiwiZm9vIl0sIm1hcHBpbmdzIjoiQUFBQSxJQUFJQSxJQUFNLFdBQ04sU0FBU0MsSUFBS0QsS0FDVixPQUFPQSxJQUdYLE9BQU9DIn0=\n"); + "//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMTMyMy9zYW1wbGUuanMiXSwibmFtZXMiOlsiYmFyIiwiZm9vIl0sIm1hcHBpbmdzIjoiQUFBQSxJQUFJQSxJQUFNLFdBQ04sU0FBU0MsSUFBS0QsS0FDVixPQUFPQSxJQUdYLE9BQU9DLElBTEQifQ==\n"); done(); }); }); @@ -192,7 +192,7 @@ describe("bin/uglifyjs", function () { assert.strictEqual(stdout, [ "var bar=function(){function foo(bar){return bar}return foo}();", - "//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMTMyMy9zYW1wbGUuanMiXSwibmFtZXMiOlsiYmFyIiwiZm9vIl0sIm1hcHBpbmdzIjoiQUFBQSxJQUFJQSxJQUFNLFdBQ04sU0FBU0MsSUFBS0QsS0FDVixPQUFPQSxJQUdYLE9BQU9DIn0=", + "//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMTMyMy9zYW1wbGUuanMiXSwibmFtZXMiOlsiYmFyIiwiZm9vIl0sIm1hcHBpbmdzIjoiQUFBQSxJQUFJQSxJQUFNLFdBQ04sU0FBU0MsSUFBS0QsS0FDVixPQUFPQSxJQUdYLE9BQU9DLElBTEQifQ==", "", ].join("\n")); assert.strictEqual(stderr, "WARN: inline source map not found\n"); From c615a1e80aa0960fa7eb888e1bc085e70d3c7bfe Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 12 Jul 2017 02:55:57 +0800 Subject: [PATCH 04/11] fix gzip stream in `test/benchmark.js` (#2228) --- test/benchmark.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/benchmark.js b/test/benchmark.js index 569d4476..8b20ec13 100644 --- a/test/benchmark.js +++ b/test/benchmark.js @@ -64,14 +64,13 @@ urls.forEach(function(url) { }).pipe(uglifyjs.stdin); uglifyjs.stdout.on("data", function(data) { results[url].output += data.length; - }).pipe(createHash("sha1")).on("data", function(data) { - results[url].sha1 = data.toString("hex"); - done(); - }); - uglifyjs.stdout.pipe(zlib.createGzip({ + }).pipe(zlib.createGzip({ level: zlib.Z_BEST_COMPRESSION })).on("data", function(data) { results[url].gzip += data.length; + }).pipe(createHash("sha1")).on("data", function(data) { + results[url].sha1 = data.toString("hex"); + done(); }); uglifyjs.stderr.setEncoding("utf8"); uglifyjs.stderr.on("data", function(data) { From 458e3e15f0f315dcd4a99b02bed0fb740ffdfc17 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 13 Jul 2017 02:18:59 +0800 Subject: [PATCH 05/11] enhance `passes` (#2229) - remove hardcoded upper limit - continue based on node count reduction - emit verbose statistics fixes #2226 --- README.md | 2 +- lib/compress.js | 12 ++++++++- test/compress/issue-1034.js | 52 ++++++++++++++++++++----------------- 3 files changed, 40 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 621c524a..8db77e57 100644 --- a/README.md +++ b/README.md @@ -721,7 +721,7 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u compressor from discarding function names. Useful for code relying on `Function.prototype.name`. See also: the `keep_fnames` [mangle option](#mangle). -- `passes` -- default `1`. Number of times to run compress with a maximum of 3. +- `passes` -- default `1`. The maximum number of times to run compress. In some cases more than one pass leads to further compressed code. Keep in mind more passes will take more time. diff --git a/lib/compress.js b/lib/compress.js index 74fb62ec..9b79a3cb 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -148,10 +148,20 @@ merge(Compressor.prototype, { node.process_expression(true); } var passes = +this.options.passes || 1; - for (var pass = 0; pass < passes && pass < 3; ++pass) { + var last_count = 1 / 0; + for (var pass = 0; pass < passes; pass++) { if (pass > 0 || this.option("reduce_vars")) node.reset_opt_flags(this, true); node = node.transform(this); + if (passes > 1) { + var count = 0; + node.walk(new TreeWalker(function() { + count++; + })); + this.info("pass " + pass + ": last_count: " + last_count + ", count: " + count); + if (count >= last_count) break; + last_count = count; + } } if (this.option("expression")) { node.process_expression(false); diff --git a/test/compress/issue-1034.js b/test/compress/issue-1034.js index 28e47f07..f312408c 100644 --- a/test/compress/issue-1034.js +++ b/test/compress/issue-1034.js @@ -71,11 +71,13 @@ non_hoisted_function_after_return_2a: { "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:51,16]", "WARN: Dropping unused variable a [test/compress/issue-1034.js:48,20]", "WARN: Dropping unused function nope [test/compress/issue-1034.js:55,21]", + "WARN: pass 0: last_count: Infinity, count: 37", "WARN: Dropping unreachable code [test/compress/issue-1034.js:53,12]", "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:53,12]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:56,12]", "WARN: Dropping unused variable b [test/compress/issue-1034.js:51,20]", "WARN: Dropping unused variable c [test/compress/issue-1034.js:53,16]", + "WARN: pass 1: last_count: 37, count: 18", ] } @@ -109,11 +111,11 @@ non_hoisted_function_after_return_2b: { } expect_warnings: [ // duplicate warnings no longer emitted - "WARN: Dropping unreachable code [test/compress/issue-1034.js:95,16]", - "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:95,16]", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:97,12]", - "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:97,12]", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:101,12]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:97,16]", + "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:97,16]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:99,12]", + "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:99,12]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:103,12]", ] } @@ -151,10 +153,10 @@ non_hoisted_function_after_return_strict: { } expect_stdout: "8 7" expect_warnings: [ - 'WARN: Dropping unreachable code [test/compress/issue-1034.js:131,16]', - "WARN: Dropping unreachable code [test/compress/issue-1034.js:134,16]", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:137,12]", - "WARN: Dropping unused function UnusedFunction [test/compress/issue-1034.js:138,21]" + "WARN: Dropping unreachable code [test/compress/issue-1034.js:133,16]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:136,16]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:139,12]", + "WARN: Dropping unused function UnusedFunction [test/compress/issue-1034.js:140,21]", ] } @@ -194,17 +196,19 @@ non_hoisted_function_after_return_2a_strict: { } expect_stdout: "5 6" expect_warnings: [ - "WARN: Dropping unreachable code [test/compress/issue-1034.js:173,16]", - "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:173,16]", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:176,16]", - "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:176,16]", - "WARN: Dropping unused variable a [test/compress/issue-1034.js:173,20]", - "WARN: Dropping unused function nope [test/compress/issue-1034.js:180,21]", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:178,12]", - "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:178,12]", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:181,12]", - "WARN: Dropping unused variable b [test/compress/issue-1034.js:176,20]", - "WARN: Dropping unused variable c [test/compress/issue-1034.js:178,16]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:175,16]", + "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:175,16]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:178,16]", + "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:178,16]", + "WARN: Dropping unused variable a [test/compress/issue-1034.js:175,20]", + "WARN: Dropping unused function nope [test/compress/issue-1034.js:182,21]", + "WARN: pass 0: last_count: Infinity, count: 48", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:180,12]", + "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:180,12]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:183,12]", + "WARN: Dropping unused variable b [test/compress/issue-1034.js:178,20]", + "WARN: Dropping unused variable c [test/compress/issue-1034.js:180,16]", + "WARN: pass 1: last_count: 48, count: 29", ] } @@ -243,10 +247,10 @@ non_hoisted_function_after_return_2b_strict: { expect_stdout: "5 6" expect_warnings: [ // duplicate warnings no longer emitted - "WARN: Dropping unreachable code [test/compress/issue-1034.js:225,16]", - "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:225,16]", - "WARN: Dropping unreachable code [test/compress/issue-1034.js:227,12]", - "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:227,12]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:229,16]", + "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:229,16]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:231,12]", + "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:231,12]", + "WARN: Dropping unreachable code [test/compress/issue-1034.js:235,12]", ] } From 5229cb2b1b345a4cedbdeacb02f114033f4c69b7 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 14 Jul 2017 00:39:34 +0800 Subject: [PATCH 06/11] drop `unused` compound assignments (#2230) fixes #2226 --- lib/compress.js | 45 ++++++++++------- test/compress/collapse_vars.js | 4 +- test/compress/drop-unused.js | 90 +++++++++++++++++++++++++++++++++- 3 files changed, 118 insertions(+), 21 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 9b79a3cb..3a5694fc 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -790,6 +790,7 @@ merge(Compressor.prototype, { right: candidate.value }); } + candidate.write_only = false; return candidate; } // These node types have child nodes that execute sequentially, @@ -2177,7 +2178,12 @@ merge(Compressor.prototype, { var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs; var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars; if (!drop_funcs && !drop_vars) return; - var assign_as_unused = !/keep_assign/.test(compressor.option("unused")); + var assign_as_unused = /keep_assign/.test(compressor.option("unused")) ? return_false : function(node) { + if (node instanceof AST_Assign && (node.write_only || node.operator == "=")) { + return node.left; + } + if (node instanceof AST_Unary && node.write_only) return node.expression; + }; var in_use = []; var in_use_ids = Object.create(null); // avoid expensive linear scans of in_use if (self instanceof AST_Toplevel && compressor.top_retain) { @@ -2227,12 +2233,8 @@ merge(Compressor.prototype, { }); return true; } - if (assign_as_unused - && node instanceof AST_Assign - && node.operator == "=" - && node.left instanceof AST_SymbolRef - && scope === self) { - node.right.walk(tw); + if (assign_as_unused(node) instanceof AST_SymbolRef && scope === self) { + if (node instanceof AST_Assign) node.right.walk(tw); return true; } if (node instanceof AST_SymbolRef) { @@ -2396,14 +2398,17 @@ merge(Compressor.prototype, { }); } } - if (drop_vars && assign_as_unused - && node instanceof AST_Assign - && node.operator == "=" - && node.left instanceof AST_SymbolRef) { - var def = node.left.definition(); - if (!(def.id in in_use_ids) + if (drop_vars) { + var def = assign_as_unused(node); + if (def instanceof AST_SymbolRef + && !((def = def.definition()).id in in_use_ids) && self.variables.get(def.name) === def) { - return maintain_this_binding(tt.parent(), node, node.right.transform(tt)); + if (node instanceof AST_Assign) { + return maintain_this_binding(tt.parent(), node, node.right.transform(tt)); + } + return make_node(AST_Number, node, { + value: 0 + }); } } // certain combination of unused name + side effect leads to: @@ -2642,7 +2647,10 @@ merge(Compressor.prototype, { return make_sequence(this, [ left, right ]); } }); - def(AST_Assign, return_this); + def(AST_Assign, function(compressor){ + this.write_only = !this.left.has_side_effects(compressor); + return this; + }); def(AST_Conditional, function(compressor){ var consequent = this.consequent.drop_side_effect_free(compressor); var alternative = this.alternative.drop_side_effect_free(compressor); @@ -2663,7 +2671,10 @@ merge(Compressor.prototype, { return node; }); def(AST_Unary, function(compressor, first_in_statement){ - if (unary_side_effects(this.operator)) return this; + if (unary_side_effects(this.operator)) { + this.write_only = !this.expression.has_side_effects(compressor); + return this; + } if (this.operator == "typeof" && this.expression instanceof AST_SymbolRef) return null; var expression = this.expression.drop_side_effect_free(compressor, first_in_statement); if (first_in_statement @@ -3485,7 +3496,7 @@ merge(Compressor.prototype, { operator: car.operator, expression: left }); - } + } else car.write_only = false; if (parent) { parent[field] = car; expressions[i] = expressions[j]; diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 10c403fa..7686addf 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -863,7 +863,7 @@ collapse_vars_unary: { input: { function f0(o, p) { var x = o[p]; - delete x; + return delete x; } function f1(n) { var k = !!n; @@ -893,7 +893,7 @@ collapse_vars_unary: { expect: { function f0(o, p) { var x = o[p]; - delete x; + return delete x; } function f1(n) { return n > +!!n diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index a44107ae..34d47d0d 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -1090,6 +1090,7 @@ var_catch_toplevel: { a--; try { a++; + x(); } catch(a) { if (a) var a; var a = 10; @@ -1099,9 +1100,8 @@ var_catch_toplevel: { } expect: { !function() { - a--; try { - a++; + x(); } catch(a) { var a; } @@ -1153,3 +1153,89 @@ issue_2105: { } expect_stdout: "PASS" } + +issue_2226_1: { + options = { + side_effects: true, + unused: true, + } + input: { + function f1() { + var a = b; + a += c; + } + function f2(a) { + a <<= b; + } + function f3(a) { + --a; + } + function f4() { + var a = b; + return a *= c; + } + function f5(a) { + x(a /= b); + } + } + expect: { + function f1() { + b; + c; + } + function f2(a) { + b; + } + function f3(a) { + 0; + } + function f4() { + var a = b; + return a *= c; + } + function f5(a) { + x(a /= b); + } + } +} + +issue_2226_2: { + options = { + cascade: true, + sequences: true, + side_effects: true, + unused: true, + } + input: { + console.log(function(a, b) { + a += b; + return a; + }(1, 2)); + } + expect: { + console.log(function(a, b) { + return a += b; + }(1, 2)); + } + expect_stdout: "3" +} + +issue_2226_3: { + options = { + collapse_vars: true, + side_effects: true, + unused: true, + } + input: { + console.log(function(a, b) { + a += b; + return a; + }(1, 2)); + } + expect: { + console.log(function(a, b) { + return a += 2; + }(1)); + } + expect_stdout: "3" +} From 9282e7b0c6f20bc95ba3d2bab2bbaccebab03c9a Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 14 Jul 2017 19:52:01 +0800 Subject: [PATCH 07/11] fix `unsafe` `evaluate` of `Object` static methods (#2232) fixes #2231 --- lib/compress.js | 8 +++----- test/compress/evaluate.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 3a5694fc..0330c682 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1853,10 +1853,6 @@ merge(Compressor.prototype, { "isFinite", "isNaN", ], - Object: [ - "keys", - "getOwnPropertyNames", - ], String: [ "fromCharCode", ], @@ -3496,7 +3492,9 @@ merge(Compressor.prototype, { operator: car.operator, expression: left }); - } else car.write_only = false; + } else { + car.write_only = false; + } if (parent) { parent[field] = car; expressions[i] = expressions[j]; diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 38e9cdcb..1c737060 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -1157,3 +1157,31 @@ issue_2207_3: { } expect_stdout: true } + +issue_2231_1: { + options = { + evaluate: true, + unsafe: true, + } + input: { + console.log(Object.keys(void 0)); + } + expect: { + console.log(Object.keys(void 0)); + } + expect_stdout: true +} + +issue_2231_2: { + options = { + evaluate: true, + unsafe: true, + } + input: { + console.log(Object.getOwnPropertyNames(null)); + } + expect: { + console.log(Object.getOwnPropertyNames(null)); + } + expect_stdout: true +} From a5ffe2c23fdfaf13f3466a01d9dd9d590c5e8672 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sat, 15 Jul 2017 15:16:11 +0800 Subject: [PATCH 08/11] drop `unused` builtin globals under `unsafe` (#2236) fixes #2233 --- lib/compress.js | 85 ++++++++++++++++++++------------------ lib/scope.js | 8 ---- test/compress/dead-code.js | 79 +++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 48 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 0330c682..6dc7b725 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -689,6 +689,16 @@ merge(Compressor.prototype, { return false; } + function is_undeclared_ref(node) { + return node instanceof AST_SymbolRef && node.definition().undeclared; + } + + var global_names = makePredicate("Array Boolean console Error Function Math Number RegExp Object String"); + AST_SymbolRef.DEFMETHOD("is_declared", function(compressor) { + return !this.definition().undeclared + || compressor.option("unsafe") && global_names(this.name); + }); + function tighten_body(statements, compressor) { var CHANGED, max_iter = 10; do { @@ -758,7 +768,7 @@ merge(Compressor.prototype, { || node instanceof AST_Call && lhs instanceof AST_PropAccess && lhs.equivalent_to(node.expression) || node instanceof AST_Debugger || node instanceof AST_IterationStatement && !(node instanceof AST_For) - || node instanceof AST_SymbolRef && node.undeclared() + || node instanceof AST_SymbolRef && !node.is_declared(compressor) || node instanceof AST_Try || node instanceof AST_With || parent instanceof AST_For && node !== parent.init) { @@ -1320,12 +1330,12 @@ merge(Compressor.prototype, { // returns true if this node may be null, undefined or contain `AST_Accessor` (function(def) { AST_Node.DEFMETHOD("may_throw_on_access", function(compressor) { - var pure_getters = compressor.option("pure_getters"); - return !pure_getters || this._throw_on_access(pure_getters); + return !compressor.option("pure_getters") + || this._dot_throw(compressor); }); - function is_strict(pure_getters) { - return /strict/.test(pure_getters); + function is_strict(compressor) { + return /strict/.test(compressor.option("pure_getters")); } def(AST_Node, is_strict); @@ -1333,8 +1343,8 @@ merge(Compressor.prototype, { def(AST_Undefined, return_true); def(AST_Constant, return_false); def(AST_Array, return_false); - def(AST_Object, function(pure_getters) { - if (!is_strict(pure_getters)) return false; + def(AST_Object, function(compressor) { + if (!is_strict(compressor)) return false; for (var i = this.properties.length; --i >=0;) if (this.properties[i].value instanceof AST_Accessor) return true; return false; @@ -1344,37 +1354,38 @@ merge(Compressor.prototype, { def(AST_UnaryPrefix, function() { return this.operator == "void"; }); - def(AST_Binary, function(pure_getters) { + def(AST_Binary, function(compressor) { switch (this.operator) { case "&&": - return this.left._throw_on_access(pure_getters); + return this.left._dot_throw(compressor); case "||": - return this.left._throw_on_access(pure_getters) - && this.right._throw_on_access(pure_getters); + return this.left._dot_throw(compressor) + && this.right._dot_throw(compressor); default: return false; } }) - def(AST_Assign, function(pure_getters) { + def(AST_Assign, function(compressor) { return this.operator == "=" - && this.right._throw_on_access(pure_getters); + && this.right._dot_throw(compressor); }) - def(AST_Conditional, function(pure_getters) { - return this.consequent._throw_on_access(pure_getters) - || this.alternative._throw_on_access(pure_getters); + def(AST_Conditional, function(compressor) { + return this.consequent._dot_throw(compressor) + || this.alternative._dot_throw(compressor); }) - def(AST_Sequence, function(pure_getters) { - return this.expressions[this.expressions.length - 1]._throw_on_access(pure_getters); + def(AST_Sequence, function(compressor) { + return this.expressions[this.expressions.length - 1]._dot_throw(compressor); }); - def(AST_SymbolRef, function(pure_getters) { + def(AST_SymbolRef, function(compressor) { if (this.is_undefined) return true; - if (!is_strict(pure_getters)) return false; + if (!is_strict(compressor)) return false; + if (is_undeclared_ref(this) && this.is_declared(compressor)) return false; if (this.is_immutable()) return false; var fixed = this.fixed_value(); - return !fixed || fixed._throw_on_access(pure_getters); + return !fixed || fixed._dot_throw(compressor); }); })(function(node, func) { - node.DEFMETHOD("_throw_on_access", func); + node.DEFMETHOD("_dot_throw", func); }); /* -----[ boolean/negation helpers ]----- */ @@ -1735,11 +1746,8 @@ merge(Compressor.prototype, { }); var global_objs = { Array: Array, - Boolean: Boolean, Math: Math, Number: Number, - RegExp: RegExp, - Object: Object, String: String, }; function convert_to_predicate(obj) { @@ -1776,7 +1784,7 @@ merge(Compressor.prototype, { } var exp = this.expression; var val; - if (exp instanceof AST_SymbolRef && exp.undeclared()) { + if (is_undeclared_ref(exp)) { if (!(static_values[exp.name] || return_false)(key)) return this; val = global_objs[exp.name]; } else { @@ -1868,7 +1876,7 @@ merge(Compressor.prototype, { } var val; var e = exp.expression; - if (e instanceof AST_SymbolRef && e.undeclared()) { + if (is_undeclared_ref(e)) { if (!(static_fns[e.name] || return_false)(key)) return this; val = global_objs[e.name]; } else { @@ -2050,7 +2058,7 @@ merge(Compressor.prototype, { || this.expression.has_side_effects(compressor); }); def(AST_SymbolRef, function(compressor){ - return this.undeclared(); + return !this.is_declared(compressor); }); def(AST_SymbolDeclaration, return_false); def(AST_Object, function(compressor){ @@ -2684,8 +2692,8 @@ merge(Compressor.prototype, { } return expression; }); - def(AST_SymbolRef, function() { - return this.undeclared() ? this : null; + def(AST_SymbolRef, function(compressor) { + return this.is_declared(compressor) ? null : this; }); def(AST_Object, function(compressor, first_in_statement){ var values = trim(this.properties, compressor, first_in_statement); @@ -3147,7 +3155,7 @@ merge(Compressor.prototype, { self.args.length = last; } if (compressor.option("unsafe")) { - if (exp instanceof AST_SymbolRef && exp.undeclared()) { + if (is_undeclared_ref(exp)) { switch (exp.name) { case "Array": if (self.args.length != 1) { @@ -3274,8 +3282,7 @@ merge(Compressor.prototype, { } } if (compressor.option("unsafe_Func") - && exp instanceof AST_SymbolRef - && exp.undeclared() + && is_undeclared_ref(exp) && exp.name == "Function") { // new Function() => function(){} if (self.args.length == 0) return make_node(AST_Function, self, { @@ -3392,9 +3399,7 @@ merge(Compressor.prototype, { while (name.expression) { name = name.expression; } - if (name instanceof AST_SymbolRef - && name.name == "console" - && name.undeclared()) { + if (is_undeclared_ref(name) && name.name == "console") { return make_node(AST_Undefined, self).optimize(compressor); } } @@ -3415,7 +3420,7 @@ merge(Compressor.prototype, { OPT(AST_New, function(self, compressor){ if (compressor.option("unsafe")) { var exp = self.expression; - if (exp instanceof AST_SymbolRef && exp.undeclared()) { + if (is_undeclared_ref(exp)) { switch (exp.name) { case "Object": case "RegExp": @@ -3709,7 +3714,7 @@ merge(Compressor.prototype, { && self.right instanceof AST_UnaryPrefix && self.right.operator == "typeof") { var expr = self.right.expression; - if (expr instanceof AST_SymbolRef ? !expr.undeclared() + if (expr instanceof AST_SymbolRef ? expr.is_declared(compressor) : !(expr instanceof AST_PropAccess && compressor.option("ie8"))) { self.right = expr; self.left = make_node(AST_Undefined, self.left).optimize(compressor); @@ -4035,7 +4040,7 @@ merge(Compressor.prototype, { } // testing against !self.scope.uses_with first is an optimization if (!compressor.option("ie8") - && self.undeclared() + && is_undeclared_ref(self) && (!self.scope.uses_with || !compressor.find_parent(AST_With))) { switch (self.name) { case "undefined": @@ -4454,7 +4459,7 @@ merge(Compressor.prototype, { && self.expression instanceof AST_Dot && self.expression.property == "prototype") { var exp = self.expression.expression; - if (exp instanceof AST_SymbolRef && exp.undeclared()) switch (exp.name) { + if (is_undeclared_ref(exp)) switch (exp.name) { case "Array": self.expression = make_node(AST_Array, self.expression, { elements: [] diff --git a/lib/scope.js b/lib/scope.js index f8ecedb5..df7b2076 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -373,14 +373,6 @@ AST_Symbol.DEFMETHOD("unreferenced", function(){ && !(this.scope.uses_eval || this.scope.uses_with); }); -AST_Symbol.DEFMETHOD("undeclared", function(){ - return this.definition().undeclared; -}); - -AST_LabelRef.DEFMETHOD("undeclared", return_false); - -AST_Label.DEFMETHOD("undeclared", return_false); - AST_Symbol.DEFMETHOD("definition", function(){ return this.thedef; }); diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index 0fcbbe43..abf5297c 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -230,3 +230,82 @@ accessor: { } expect: {} } + +issue_2233_1: { + options = { + pure_getters: "strict", + side_effects: true, + unsafe: true, + } + input: { + Array.isArray; + Boolean; + console.log; + Error.name; + Function.length; + Math.random; + Number.isNaN; + RegExp; + Object.defineProperty; + String.fromCharCode; + } + expect: {} + expect_stdout: true +} + +issue_2233_2: { + options = { + pure_getters: "strict", + reduce_vars: true, + side_effects: true, + unsafe: true, + unused: true, + } + input: { + var RegExp; + Array.isArray; + RegExp; + UndeclaredGlobal; + function foo() { + var Number; + AnotherUndeclaredGlobal; + Math.sin; + Number.isNaN; + } + } + expect: { + var RegExp; + UndeclaredGlobal; + function foo() { + var Number; + AnotherUndeclaredGlobal; + Number.isNaN; + } + } +} + +issue_2233_3: { + options = { + pure_getters: "strict", + reduce_vars: true, + side_effects: true, + toplevel: true, + unsafe: true, + unused: true, + } + input: { + var RegExp; + Array.isArray; + RegExp; + UndeclaredGlobal; + function foo() { + var Number; + AnotherUndeclaredGlobal; + Math.sin; + Number.isNaN; + } + } + expect: { + UndeclaredGlobal; + } +} From 9e1da9235ea498760f45709848b76469f5b2a585 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sat, 15 Jul 2017 22:50:59 +0800 Subject: [PATCH 09/11] ensure `ie8` works with mangled properties (#2238) fixes #2234 --- lib/compress.js | 13 ++----------- lib/output.js | 22 +++++++++++++++------- test/compress/properties.js | 8 ++++++-- test/mocha/let.js | 35 +++++++++++++++++++++++++++-------- 4 files changed, 50 insertions(+), 28 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 6dc7b725..7a16ba86 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -4395,7 +4395,7 @@ merge(Compressor.prototype, { var prop = self.property; if (prop instanceof AST_String && compressor.option("properties")) { prop = prop.getValue(); - if (RESERVED_WORDS(prop) ? !compressor.option("ie8") : is_identifier_string(prop)) { + if (is_identifier_string(prop)) { return make_node(AST_Dot, self, { expression : self.expression, property : prop @@ -4432,19 +4432,10 @@ merge(Compressor.prototype, { if (def) { return def.optimize(compressor); } - var prop = self.property; - if (RESERVED_WORDS(prop) && compressor.option("ie8")) { - return make_node(AST_Sub, self, { - expression : self.expression, - property : make_node(AST_String, self, { - value: prop - }) - }).optimize(compressor); - } if (compressor.option("unsafe") && self.expression instanceof AST_Object) { var values = self.expression.properties; for (var i = values.length; --i >= 0;) { - if (values[i].key === prop) { + if (values[i].key === self.property) { var value = values[i].value; if (value instanceof AST_Function ? !value.contains_this() : !value.has_side_effects(compressor)) { var obj = self.expression.clone(); diff --git a/lib/output.js b/lib/output.js index 6ee96b31..edb8d182 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1146,15 +1146,23 @@ function OutputStream(options) { DEFPRINT(AST_Dot, function(self, output){ var expr = self.expression; expr.print(output); - if (expr instanceof AST_Number && expr.getValue() >= 0) { - if (!/[xa-f.)]/i.test(output.last())) { - output.print("."); + var prop = self.property; + if (output.option("ie8") && RESERVED_WORDS(prop)) { + output.print("["); + output.add_mapping(self.end); + output.print_string(prop); + output.print("]"); + } else { + if (expr instanceof AST_Number && expr.getValue() >= 0) { + if (!/[xa-f.)]/i.test(output.last())) { + output.print("."); + } } + output.print("."); + // the name after dot would be mapped about here. + output.add_mapping(self.end); + output.print_name(prop); } - output.print("."); - // the name after dot would be mapped about here. - output.add_mapping(self.end); - output.print_name(self.property); }); DEFPRINT(AST_Sub, function(self, output){ self.expression.print(output); diff --git a/test/compress/properties.js b/test/compress/properties.js index a5527de3..dda2e74f 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -13,8 +13,10 @@ keep_properties: { dot_properties: { options = { properties: true, + } + beautify = { ie8: true, - }; + } input: { a["foo"] = "bar"; a["if"] = "if"; @@ -36,8 +38,10 @@ dot_properties: { dot_properties_es5: { options = { properties: true, + } + beautify = { ie8: false, - }; + } input: { a["foo"] = "bar"; a["if"] = "if"; diff --git a/test/mocha/let.js b/test/mocha/let.js index 23909986..8685746b 100644 --- a/test/mocha/let.js +++ b/test/mocha/let.js @@ -2,16 +2,17 @@ var Uglify = require('../../'); var assert = require("assert"); describe("let", function() { - it("Should not produce reserved keywords as variable name in mangle", function(done) { - this.timeout(10000); - + this.timeout(30000); + it("Should not produce reserved keywords as variable name in mangle", function() { // Produce a lot of variables in a function and run it through mangle. var s = '"dddddeeeeelllllooooottttt"; function foo() {'; for (var i = 0; i < 18000; i++) { s += "var v" + i + "=0;"; } s += '}'; - var result = Uglify.minify(s, {compress: false}); + var result = Uglify.minify(s, { + compress: false + }).code; // Verify that select keywords and reserved keywords not produced [ @@ -19,7 +20,7 @@ describe("let", function() { "let", "var", ].forEach(function(name) { - assert.strictEqual(result.code.indexOf("var " + name + "="), -1); + assert.strictEqual(result.indexOf("var " + name + "="), -1); }); // Verify that the variable names that appeared immediately before @@ -30,9 +31,27 @@ describe("let", function() { "eet", "fet", "rar", "oar", ].forEach(function(name) { - assert.ok(result.code.indexOf("var " + name + "=") >= 0); + assert.notStrictEqual(result.indexOf("var " + name + "="), -1); + }); + }); + it("Should quote mangled properties that are reserved keywords", function() { + var s = '"rrrrrnnnnniiiiiaaaaa";'; + for (var i = 0; i < 18000; i++) { + s += "v.b" + i + ";"; + } + var result = Uglify.minify(s, { + compress: false, + ie8: true, + mangle: { + properties: true, + } + }).code; + [ + "in", + "var", + ].forEach(function(name) { + assert.notStrictEqual(result.indexOf(name), -1); + assert.notStrictEqual(result.indexOf('v["' + name + '"]'), -1); }); - - done(); }); }); From b35dfc2599ad84b21d278ca2c330e46b5e58c956 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sat, 15 Jul 2017 23:50:27 +0800 Subject: [PATCH 10/11] reject malformed CLI parameters (#2239) fixes #2237 --- bin/uglifyjs | 22 +++++++++++++--------- lib/propmangle.js | 2 +- test/mocha/cli.js | 26 ++++++++++++++++++++++---- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index 68d67c2f..8cbb3cad 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -35,11 +35,11 @@ else if (process.argv.indexOf("options") >= 0) program.helpInformation = functio } return text.join("\n"); }; -program.option("-p, --parse ", "Specify parser options.", parse_js("parse", true)); -program.option("-c, --compress [options]", "Enable compressor/specify compressor options.", parse_js("compress", true)); -program.option("-m, --mangle [options]", "Mangle names/specify mangler options.", parse_js("mangle", true)); -program.option("--mangle-props [options]", "Mangle properties/specify mangler options.", parse_js("mangle-props", true)); -program.option("-b, --beautify [options]", "Beautify output/specify output options.", parse_js("beautify", true)); +program.option("-p, --parse ", "Specify parser options.", parse_js()); +program.option("-c, --compress [options]", "Enable compressor/specify compressor options.", parse_js()); +program.option("-m, --mangle [options]", "Mangle names/specify mangler options.", parse_js()); +program.option("--mangle-props [options]", "Mangle properties/specify mangler options.", parse_js()); +program.option("-b, --beautify [options]", "Beautify output/specify output options.", parse_js()); program.option("-o, --output ", "Output file (default STDOUT)."); program.option("--comments [filter]", "Preserve copyright comments in the output."); program.option("--config-file ", "Read minify() options from JSON file."); @@ -310,7 +310,7 @@ function read_file(path, default_value) { } } -function parse_js(flag, constants) { +function parse_js(flag) { return function(value, options) { options = options || {}; try { @@ -328,7 +328,7 @@ function parse_js(flag, constants) { if (node instanceof UglifyJS.AST_Assign) { var name = node.left.print_to_string(); var value = node.right; - if (!constants) { + if (flag) { options[name] = value; } else if (value instanceof UglifyJS.AST_Array) { options[name] = value.elements.map(to_string); @@ -351,14 +351,18 @@ function parse_js(flag, constants) { } })); } catch(ex) { - options[value] = null; + if (flag) { + fatal("Error parsing arguments for '" + flag + "': " + value); + } else { + options[value] = null; + } } return options; } } function parse_source_map() { - var parse = parse_js("sourceMap", true); + var parse = parse_js(); return function(value, options) { var hasContent = options && "content" in options; var settings = parse(value, options); diff --git a/lib/propmangle.js b/lib/propmangle.js index f17909d7..49490755 100644 --- a/lib/propmangle.js +++ b/lib/propmangle.js @@ -76,7 +76,7 @@ function mangle_properties(ast, options) { only_cache: false, regex: null, reserved: null, - }); + }, true); var reserved = options.reserved; if (!Array.isArray(reserved)) reserved = []; diff --git a/test/mocha/cli.js b/test/mocha/cli.js index 8bfdc85a..1d847dc9 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -593,8 +593,8 @@ describe("bin/uglifyjs", function () { done(); }); }); - it("Should work with --mangle reserved=[]", function (done) { - var command = uglifyjscmd + ' test/input/issue-505/input.js -m reserved=[callback]'; + it("Should work with --mangle reserved=[]", function(done) { + var command = uglifyjscmd + " test/input/issue-505/input.js -m reserved=[callback]"; exec(command, function (err, stdout) { if (err) throw err; @@ -603,8 +603,8 @@ describe("bin/uglifyjs", function () { done(); }); }); - it("Should work with --mangle reserved=false", function (done) { - var command = uglifyjscmd + ' test/input/issue-505/input.js -m reserved=false'; + it("Should work with --mangle reserved=false", function(done) { + var command = uglifyjscmd + " test/input/issue-505/input.js -m reserved=false"; exec(command, function (err, stdout) { if (err) throw err; @@ -613,4 +613,22 @@ describe("bin/uglifyjs", function () { done(); }); }); + it("Should fail with --mangle-props reserved=[in]", function(done) { + var command = uglifyjscmd + " test/input/issue-505/input.js --mangle-props reserved=[in]"; + exec(command, function (err, stdout, stderr) { + assert.ok(err); + assert.strictEqual(stdout, ""); + assert.ok(/^Supported options:\n[\s\S]*?\nERROR: `reserved=\[in]` is not a supported option/.test(stderr), stderr); + done(); + }); + }); + it("Should fail with --define a-b", function(done) { + var command = uglifyjscmd + " test/input/issue-505/input.js --define a-b"; + exec(command, function (err, stdout, stderr) { + assert.ok(err); + assert.strictEqual(stdout, ""); + assert.strictEqual(stderr, "Error parsing arguments for 'define': a-b\n"); + done(); + }); + }); }); From 4e12a6f740bfc3643516679d8faf8f5296d980d0 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 16 Jul 2017 11:05:53 +0800 Subject: [PATCH 11/11] v3.0.25 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1185c8cc..8132bb72 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.0.24", + "version": "3.0.25", "engines": { "node": ">=0.8.0" },