From 0692435f01aad54fb586d24ad0c118d70b5c26ee Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 23 Jun 2017 04:14:30 +0800 Subject: [PATCH 1/9] fix for-in loop parsing (#2144) --- lib/parse.js | 8 ++++++-- test/input/invalid/for-in_1.js | 4 ++++ test/input/invalid/for-in_2.js | 4 ++++ test/mocha/cli.js | 30 ++++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 test/input/invalid/for-in_1.js create mode 100644 test/input/invalid/for-in_2.js diff --git a/lib/parse.js b/lib/parse.js index 553568e1..e2dd04b6 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1010,8 +1010,12 @@ function parse($TEXT, options) { ? (next(), var_(true)) : expression(true, true); if (is("operator", "in")) { - if (init instanceof AST_Var && init.definitions.length > 1) - croak("Only one variable declaration allowed in for..in loop"); + if (init instanceof AST_Var) { + if (init.definitions.length > 1) + croak("Only one variable declaration allowed in for..in loop", init.start.line, init.start.col, init.start.pos); + } else if (!is_assignable(init)) { + croak("Invalid left-hand side in for..in loop", init.start.line, init.start.col, init.start.pos); + } next(); return for_in(init); } diff --git a/test/input/invalid/for-in_1.js b/test/input/invalid/for-in_1.js new file mode 100644 index 00000000..4d872d19 --- /dev/null +++ b/test/input/invalid/for-in_1.js @@ -0,0 +1,4 @@ +var a, b = [1, 2]; +for (1, 2, a in b) { + console.log(a, b[a]); +} diff --git a/test/input/invalid/for-in_2.js b/test/input/invalid/for-in_2.js new file mode 100644 index 00000000..57d861e9 --- /dev/null +++ b/test/input/invalid/for-in_2.js @@ -0,0 +1,4 @@ +var c = [1, 2]; +for (var a, b in c) { + console.log(a, c[a]); +} diff --git a/test/mocha/cli.js b/test/mocha/cli.js index b54044eb..4c4943c6 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -518,6 +518,36 @@ describe("bin/uglifyjs", function () { done(); }); }); + it("Should throw syntax error (for-in init)", function(done) { + var command = uglifyjscmd + ' test/input/invalid/for-in_1.js'; + + exec(command, function (err, stdout, stderr) { + assert.ok(err); + assert.strictEqual(stdout, ""); + assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ + "Parse error at test/input/invalid/for-in_1.js:2,5", + "for (1, 2, a in b) {", + " ^", + "ERROR: Invalid left-hand side in for..in loop" + ].join("\n")); + done(); + }); + }); + it("Should throw syntax error (for-in var)", function(done) { + var command = uglifyjscmd + ' test/input/invalid/for-in_2.js'; + + exec(command, function (err, stdout, stderr) { + assert.ok(err); + assert.strictEqual(stdout, ""); + assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [ + "Parse error at test/input/invalid/for-in_2.js:2,5", + "for (var a, b in c) {", + " ^", + "ERROR: Only one variable declaration allowed in for..in loop" + ].join("\n")); + done(); + }); + }); it("Should handle literal string as source map input", function(done) { var command = [ uglifyjscmd, From 3d5bc081851897648ccc71009a2138240fe41fa0 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 23 Jun 2017 04:44:57 +0800 Subject: [PATCH 2/9] fix `reduce_vars` on `this` (#2145) fixes #2140 --- lib/compress.js | 10 +++++----- test/compress/reduce_vars.js | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index aa7affc4..c332282f 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -279,11 +279,11 @@ merge(Compressor.prototype, { var reduce_vars = rescan && compressor.option("reduce_vars"); var safe_ids = Object.create(null); var suppressor = new TreeWalker(function(node) { - if (node instanceof AST_Symbol) { - var d = node.definition(); - if (node instanceof AST_SymbolRef) d.references.push(node); - d.fixed = false; - } + if (!(node instanceof AST_Symbol)) return; + var d = node.definition(); + if (!d) return; + if (node instanceof AST_SymbolRef) d.references.push(node); + d.fixed = false; }); var tw = new TreeWalker(function(node, descend) { node._squeezed = false; diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index cef29832..5906a971 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -2575,3 +2575,28 @@ accessor: { } expect_stdout: "1 1" } + +for_in_prop: { + options = { + reduce_vars: true, + } + input: { + var a = { + foo: function() { + for (this.b in [1, 2]); + } + }; + a.foo(); + console.log(a.b); + } + expect: { + var a = { + foo: function() { + for (this.b in [1, 2]); + } + }; + a.foo(); + console.log(a.b); + } + expect_stdout: "1" +} From b3a57ff019bf5c64783f55e581aa4270a52d9d13 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 23 Jun 2017 06:59:53 +0800 Subject: [PATCH 3/9] minimise `reduce_vars` cloning overhead (#2148) --- lib/compress.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index c332282f..8890759f 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -3887,7 +3887,7 @@ merge(Compressor.prototype, { var d = self.definition(); var fixed = self.fixed_value(); if (fixed instanceof AST_Defun) { - d.fixed = fixed = make_node(AST_Function, fixed, fixed).clone(true); + d.fixed = fixed = make_node(AST_Function, fixed, fixed); } if (compressor.option("unused") && fixed instanceof AST_Function @@ -3895,7 +3895,7 @@ merge(Compressor.prototype, { && !(d.scope.uses_arguments && d.orig[0] instanceof AST_SymbolFunarg) && !d.scope.uses_eval && compressor.find_parent(AST_Scope) === fixed.parent_scope) { - return fixed; + return fixed.clone(true); } if (compressor.option("evaluate") && fixed) { if (d.should_replace === undefined) { From d58b184835d9c2316bfb4411d0907ebb4446aaf0 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 23 Jun 2017 13:11:40 +0800 Subject: [PATCH 4/9] refactor `Compressor.toplevel` (#2149) --- lib/compress.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 8890759f..c24510f6 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -124,13 +124,13 @@ function Compressor(options, false_by_default) { }; } var toplevel = this.options["toplevel"]; - if (typeof toplevel == "string") { - this.toplevel.funcs = /funcs/.test(toplevel); - this.toplevel.vars = /vars/.test(toplevel); - } else { - this.toplevel = toplevel ? return_true : return_false; - this.toplevel.funcs = this.toplevel.vars = toplevel; - } + this.toplevel = typeof toplevel == "string" ? { + funcs: /funcs/.test(toplevel), + vars: /vars/.test(toplevel) + } : { + funcs: toplevel, + vars: toplevel + }; var sequences = this.options["sequences"]; this.sequences_limit = sequences == 1 ? 800 : sequences | 0; this.warnings_produced = {}; @@ -139,11 +139,11 @@ function Compressor(options, false_by_default) { Compressor.prototype = new TreeTransformer; merge(Compressor.prototype, { option: function(key) { return this.options[key] }, - toplevel: function(def) { - for (var i = 0, len = def.orig.length; i < len; i++) + exposed: function(def) { + if (def.global) for (var i = 0, len = def.orig.length; i < len; i++) if (!this.toplevel[def.orig[i] instanceof AST_SymbolDefun ? "funcs" : "vars"]) - return false; - return true; + return true; + return false; }, compress: function(node) { if (this.option("expression")) { @@ -345,7 +345,7 @@ merge(Compressor.prototype, { } if (node instanceof AST_Defun) { var d = node.name.definition(); - if (d.global && !compressor.toplevel(d) || safe_to_read(d)) { + if (compressor.exposed(d) || safe_to_read(d)) { d.fixed = false; } else { d.fixed = node; @@ -517,7 +517,7 @@ merge(Compressor.prototype, { def.escaped = false; if (def.scope.uses_eval) { def.fixed = false; - } else if (!def.global || compressor.toplevel(def)) { + } else if (!compressor.exposed(def)) { def.fixed = undefined; } else { def.fixed = false; @@ -748,7 +748,7 @@ merge(Compressor.prototype, { } if (candidate instanceof AST_VarDef) { var def = candidate.name.definition(); - if (def.references.length == 1 && (!def.global || compressor.toplevel(def))) { + if (def.references.length == 1 && !compressor.exposed(def)) { return maintain_this_binding(parent, node, candidate.value); } return make_node(AST_Assign, candidate, { @@ -810,7 +810,7 @@ merge(Compressor.prototype, { if (expr instanceof AST_VarDef) { var def = expr.name.definition(); if (def.orig.length > 1 - || def.references.length == 1 && (!def.global || compressor.toplevel(def))) { + || def.references.length == 1 && !compressor.exposed(def)) { return make_node(AST_SymbolRef, expr.name, expr.name); } } else { @@ -3918,7 +3918,7 @@ merge(Compressor.prototype, { } var name_length = d.name.length; var overhead = 0; - if (compressor.option("unused") && (!d.global || compressor.toplevel(d))) { + if (compressor.option("unused") && !compressor.exposed(d)) { overhead = (name_length + 2 + value_length) / d.references.length; } d.should_replace = value_length <= name_length + overhead ? fn : false; From dc6bcaa18eda80f31674a1f8167d7fbe997f743b Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 23 Jun 2017 15:53:13 +0800 Subject: [PATCH 5/9] synchronise `mangle.properties` for `minify()` & `test/compress` (#2151) --- lib/compress.js | 4 ++- test/compress/functions.js | 2 +- test/compress/issue-1321.js | 18 +++++----- test/compress/issue-1770.js | 66 ++++++++++++++++++------------------- test/compress/issue-747.js | 14 +++++--- test/compress/properties.js | 22 ++++++------- test/mocha/let.js | 28 ++++++++++------ test/run-tests.js | 12 ++++--- 8 files changed, 91 insertions(+), 75 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index c24510f6..b9922e18 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -3114,7 +3114,9 @@ merge(Compressor.prototype, { var comp = new Compressor(compressor.options); ast = ast.transform(comp); ast.figure_out_scope(mangle); - ast.mangle_names(); + base54.reset(); + ast.compute_char_frequency(mangle); + ast.mangle_names(mangle); var fun; ast.walk(new TreeWalker(function(node) { if (fun) return true; diff --git a/test/compress/functions.js b/test/compress/functions.js index d2640bb9..b5def8e1 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -265,7 +265,7 @@ issue_203: { } expect: { var m = {}; - var fn = Function("a", "b", "b.exports=42"); + var fn = Function("n", "o", "o.exports=42"); fn(null, m, m.exports); console.log(m.exports); } diff --git a/test/compress/issue-1321.js b/test/compress/issue-1321.js index dcbfde64..6b92291d 100644 --- a/test/compress/issue-1321.js +++ b/test/compress/issue-1321.js @@ -10,9 +10,9 @@ issue_1321_no_debug: { } expect: { var x = {}; - x.b = 1; - x["a"] = 2 * x.b; - console.log(x.b, x["a"]); + x.o = 1; + x["a"] = 2 * x.o; + console.log(x.o, x["a"]); } expect_stdout: true } @@ -30,9 +30,9 @@ issue_1321_debug: { } expect: { var x = {}; - x.a = 1; - x["_$foo$_"] = 2 * x.a; - console.log(x.a, x["_$foo$_"]); + x.o = 1; + x["_$foo$_"] = 2 * x.o; + console.log(x.o, x["_$foo$_"]); } expect_stdout: true } @@ -49,9 +49,9 @@ issue_1321_with_quoted: { } expect: { var x = {}; - x.a = 1; - x["b"] = 2 * x.a; - console.log(x.a, x["b"]); + x.o = 1; + x["x"] = 2 * x.o; + console.log(x.o, x["x"]); } expect_stdout: true } diff --git a/test/compress/issue-1770.js b/test/compress/issue-1770.js index 24134f81..f296a403 100644 --- a/test/compress/issue-1770.js +++ b/test/compress/issue-1770.js @@ -82,7 +82,7 @@ numeric_literal: { ' 42: 2,', ' "42": 3,', ' 37: 4,', - ' a: 5,', + ' o: 5,', ' 1e42: 6,', ' b: 7,', ' "1e+42": 8', @@ -92,7 +92,7 @@ numeric_literal: { '', 'console.log(obj[42], obj["42"]);', '', - 'console.log(obj[37], obj["a"], obj[37], obj["37"]);', + 'console.log(obj[37], obj["o"], obj[37], obj["37"]);', '', 'console.log(obj[1e42], obj["b"], obj["1e+42"]);', ] @@ -173,32 +173,32 @@ identifier: { } expect: { var obj = { - a: 1, - b: 2, - c: 3, - d: 4, - e: 5, - f: 6, - g: 7, - h: 8, - i: 9, - j: 10, - k: 11, - l: 12, - m: 13, - n: 14, - o: 15, - p: 16, - q: 17, - r: 18, - s: 19, - t: 20, - u: 21, - v: 22, - w: 23, - x: 24, - y: 25, - z: 26, + e: 1, + t: 2, + n: 3, + a: 4, + i: 5, + o: 6, + r: 7, + l: 8, + s: 9, + c: 10, + f: 11, + u: 12, + d: 13, + h: 14, + p: 15, + b: 16, + v: 17, + w: 18, + y: 19, + g: 20, + m: 21, + k: 22, + x: 23, + j: 24, + z: 25, + q: 26, A: 27, B: 28, C: 29, @@ -229,11 +229,11 @@ identifier: { Z: 54, $: 55, _: 56, - aa: 57, - ba: 58, - ca: 59, - da: 60, - ea: 61, + ee: 57, + te: 58, + ne: 59, + ae: 60, + ie: 61, }; } } diff --git a/test/compress/issue-747.js b/test/compress/issue-747.js index 0a4e4502..1f7079c2 100644 --- a/test/compress/issue-747.js +++ b/test/compress/issue-747.js @@ -1,37 +1,41 @@ dont_reuse_prop: { mangle_props = { regex: /asd/ - }; - + } input: { + "aaaaaaaaaabbbbb"; var obj = {}; obj.a = 123; obj.asd = 256; console.log(obj.a); } expect: { + "aaaaaaaaaabbbbb"; var obj = {}; obj.a = 123; obj.b = 256; console.log(obj.a); } + expect_stdout: "123" } unmangleable_props_should_always_be_reserved: { mangle_props = { regex: /asd/ - }; - + } input: { + "aaaaaaaaaabbbbb"; var obj = {}; obj.asd = 256; obj.a = 123; console.log(obj.a); } expect: { + "aaaaaaaaaabbbbb"; var obj = {}; obj.b = 256; obj.a = 123; console.log(obj.a); } -} \ No newline at end of file + expect_stdout: "123" +} diff --git a/test/compress/properties.js b/test/compress/properties.js index a83acc10..8126d6c6 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -135,11 +135,11 @@ mangle_properties: { a['run']({color: "blue", foo: "baz"}); } expect: { - a["a"] = "bar"; - a.b = "red"; - x = {c: 10}; - a.d(x.c, a.a); - a['d']({b: "blue", a: "baz"}); + a["o"] = "bar"; + a.a = "red"; + x = {r: 10}; + a.b(x.r, a.o); + a['b']({a: "blue", o: "baz"}); } } @@ -177,16 +177,16 @@ mangle_unquoted_properties: { function f1() { a["foo"] = "bar"; a.color = "red"; - a.b = 2; - x = {"bar": 10, c: 7}; - a.c = 9; + a.o = 2; + x = {"bar": 10, f: 7}; + a.f = 9; } function f2() { a.foo = "bar"; a['color'] = "red"; - x = {bar: 10, c: 7}; - a.c = 9; - a.b = 3; + x = {bar: 10, f: 7}; + a.f = 9; + a.o = 3; } } } diff --git a/test/mocha/let.js b/test/mocha/let.js index f41fd59b..23909986 100644 --- a/test/mocha/let.js +++ b/test/mocha/let.js @@ -2,29 +2,37 @@ var Uglify = require('../../'); var assert = require("assert"); describe("let", function() { - it("Should not produce `let` as a variable name in mangle", function(done) { + it("Should not produce reserved keywords as variable name in mangle", function(done) { this.timeout(10000); // Produce a lot of variables in a function and run it through mangle. - var s = '"use strict"; function foo() {'; - for (var i = 0; i < 21000; ++i) { + 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}); // Verify that select keywords and reserved keywords not produced - assert.strictEqual(result.code.indexOf("var let="), -1); - assert.strictEqual(result.code.indexOf("var do="), -1); - assert.strictEqual(result.code.indexOf("var var="), -1); + [ + "do", + "let", + "var", + ].forEach(function(name) { + assert.strictEqual(result.code.indexOf("var " + name + "="), -1); + }); // Verify that the variable names that appeared immediately before - // and after the erroneously generated `let` variable name still exist + // and after the erroneously generated variable name still exist // to show the test generated enough symbols. - assert(result.code.indexOf("var ket=") >= 0); - assert(result.code.indexOf("var met=") >= 0); + [ + "to", "eo", + "eet", "fet", + "rar", "oar", + ].forEach(function(name) { + assert.ok(result.code.indexOf("var " + name + "=") >= 0); + }); done(); }); }); - diff --git a/test/run-tests.js b/test/run-tests.js index 71ffe72a..6b8c9ddf 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -86,7 +86,6 @@ function run_compress_tests() { log_start_file(file); function test_case(test) { log_test(test.name); - U.base54.reset(); var output_options = test.beautify || {}; var expect; if (test.expect) { @@ -101,9 +100,6 @@ function run_compress_tests() { quote_style: 3, keep_quoted_props: true }); - if (test.mangle_props) { - input = U.mangle_properties(input, test.mangle_props); - } var options = U.defaults(test.options, { warnings: false }); @@ -118,10 +114,16 @@ function run_compress_tests() { var cmp = new U.Compressor(options, true); var output = cmp.compress(input); output.figure_out_scope(test.mangle); - if (test.mangle) { + if (test.mangle || test.mangle_props) { + U.base54.reset(); output.compute_char_frequency(test.mangle); + } + if (test.mangle) { output.mangle_names(test.mangle); } + if (test.mangle_props) { + output = U.mangle_properties(output, test.mangle_props); + } output = make_code(output, output_options); if (expect != output) { log("!!! failed\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n---EXPECTED---\n{expected}\n\n", { From 94e5e00c0321b92aca1abf170c12a02d6c3275b5 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 23 Jun 2017 20:05:31 +0800 Subject: [PATCH 6/9] refactor `compute_char_frequency()` (#2152) - minimise maintenance when updating AST - maximise code sharing between `master` & `harmony` --- lib/output.js | 1 + lib/scope.js | 123 ++++++++++++++++++-------------------------------- 2 files changed, 46 insertions(+), 78 deletions(-) diff --git a/lib/output.js b/lib/output.js index 38f58531..92c72484 100644 --- a/lib/output.js +++ b/lib/output.js @@ -501,6 +501,7 @@ function OutputStream(options) { use_asm = prev_use_asm; } }); + AST_Node.DEFMETHOD("_print", AST_Node.prototype.print); AST_Node.DEFMETHOD("print_to_string", function(options){ var s = OutputStream(options); diff --git a/lib/scope.js b/lib/scope.js index c147ce00..f8ecedb5 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -361,7 +361,8 @@ AST_Function.DEFMETHOD("next_mangled", function(options, def){ }); AST_Symbol.DEFMETHOD("unmangleable", function(options){ - return this.definition().unmangleable(options); + var def = this.definition(); + return !def || def.unmangleable(options); }); // labels are always mangleable @@ -460,103 +461,69 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){ AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){ options = this._default_mangler_options(options); - var tw = new TreeWalker(function(node){ - if (node instanceof AST_Constant) - base54.consider(node.print_to_string()); - else if (node instanceof AST_Return) - base54.consider("return"); - else if (node instanceof AST_Throw) - base54.consider("throw"); - else if (node instanceof AST_Continue) - base54.consider("continue"); - else if (node instanceof AST_Break) - base54.consider("break"); - else if (node instanceof AST_Debugger) - base54.consider("debugger"); - else if (node instanceof AST_Directive) - base54.consider(node.value); - else if (node instanceof AST_While) - base54.consider("while"); - else if (node instanceof AST_Do) - base54.consider("do while"); - else if (node instanceof AST_If) { - base54.consider("if"); - if (node.alternative) base54.consider("else"); - } - else if (node instanceof AST_Var) - base54.consider("var"); - else if (node instanceof AST_Lambda) - base54.consider("function"); - else if (node instanceof AST_For) - base54.consider("for"); - else if (node instanceof AST_ForIn) - base54.consider("for in"); - else if (node instanceof AST_Switch) - base54.consider("switch"); - else if (node instanceof AST_Case) - base54.consider("case"); - else if (node instanceof AST_Default) - base54.consider("default"); - else if (node instanceof AST_With) - base54.consider("with"); - else if (node instanceof AST_ObjectSetter) - base54.consider("set" + node.key); - else if (node instanceof AST_ObjectGetter) - base54.consider("get" + node.key); - else if (node instanceof AST_ObjectKeyVal) - base54.consider(node.key); - else if (node instanceof AST_New) - base54.consider("new"); - else if (node instanceof AST_This) - base54.consider("this"); - else if (node instanceof AST_Try) - base54.consider("try"); - else if (node instanceof AST_Catch) - base54.consider("catch"); - else if (node instanceof AST_Finally) - base54.consider("finally"); - else if (node instanceof AST_Symbol && node.unmangleable(options)) - base54.consider(node.name); - else if (node instanceof AST_Unary || node instanceof AST_Binary) - base54.consider(node.operator); - else if (node instanceof AST_Dot) - base54.consider(node.property); - }); - this.walk(tw); + try { + AST_Node.prototype.print = function(stream, force_parens) { + this._print(stream, force_parens); + if (this instanceof AST_Symbol && !this.unmangleable(options)) { + base54.consider(this.name, -1); + } else if (options.properties) { + if (this instanceof AST_Dot) { + base54.consider(this.property, -1); + } else if (this instanceof AST_Sub) { + skip_string(this.property); + } + } + }; + base54.consider(this.print_to_string(), 1); + } finally { + AST_Node.prototype.print = AST_Node.prototype._print; + } base54.sort(); + + function skip_string(node) { + if (node instanceof AST_String) { + base54.consider(node.value, -1); + } else if (node instanceof AST_Conditional) { + skip_string(node.consequent); + skip_string(node.alternative); + } else if (node instanceof AST_Sequence) { + skip_string(node.expressions[node.expressions.length - 1]); + } + } }); var base54 = (function() { - var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789"; + var leading = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_".split(""); + var digits = "0123456789".split(""); var chars, frequency; function reset() { frequency = Object.create(null); - chars = string.split("").map(function(ch){ return ch.charCodeAt(0) }); - chars.forEach(function(ch){ frequency[ch] = 0 }); + leading.forEach(function(ch) { + frequency[ch] = 0; + }); + digits.forEach(function(ch) { + frequency[ch] = 0; + }); } - base54.consider = function(str){ + base54.consider = function(str, delta) { for (var i = str.length; --i >= 0;) { - var code = str.charCodeAt(i); - if (code in frequency) ++frequency[code]; + frequency[str[i]] += delta; } }; + function compare(a, b) { + return frequency[b] - frequency[a]; + } base54.sort = function() { - chars = mergeSort(chars, function(a, b){ - if (is_digit(a) && !is_digit(b)) return 1; - if (is_digit(b) && !is_digit(a)) return -1; - return frequency[b] - frequency[a]; - }); + chars = mergeSort(leading, compare).concat(mergeSort(digits, compare)); }; base54.reset = reset; reset(); - base54.get = function(){ return chars }; - base54.freq = function(){ return frequency }; function base54(num) { var ret = "", base = 54; num++; do { num--; - ret += String.fromCharCode(chars[num % base]); + ret += chars[num % base]; num = Math.floor(num / base); base = 64; } while (num > 0); From 9db4c42380ff409b6be933083b92311522679988 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sat, 24 Jun 2017 21:30:06 +0800 Subject: [PATCH 7/9] fix `cascade` & `collapse` on property access of constants (#2158) --- lib/compress.js | 22 +++++++- test/compress/pure_getters.js | 94 +++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/lib/compress.js b/lib/compress.js index b9922e18..dc82db02 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -547,8 +547,25 @@ merge(Compressor.prototype, { return fixed(); }); + AST_SymbolRef.DEFMETHOD("is_immutable", function() { + var orig = this.definition().orig; + return orig.length == 1 && orig[0] instanceof AST_SymbolLambda; + }); + function is_lhs_read_only(lhs) { - return lhs instanceof AST_SymbolRef && lhs.definition().orig[0] instanceof AST_SymbolLambda; + if (lhs instanceof AST_SymbolRef) return lhs.definition().orig[0] instanceof AST_SymbolLambda; + if (lhs instanceof AST_PropAccess) { + lhs = lhs.expression; + if (lhs instanceof AST_SymbolRef) { + if (lhs.is_immutable()) return false; + lhs = lhs.fixed_value(); + } + if (!lhs) return true; + if (lhs instanceof AST_RegExp) return false; + if (lhs instanceof AST_Constant) return true; + return is_lhs_read_only(lhs); + } + return false; } function find_variable(compressor, name) { @@ -1287,6 +1304,7 @@ merge(Compressor.prototype, { def(AST_SymbolRef, function(pure_getters) { if (this.is_undefined) return true; if (!is_strict(pure_getters)) return false; + if (this.is_immutable()) return false; var fixed = this.fixed_value(); return !fixed || fixed._throw_on_access(pure_getters); }); @@ -3359,6 +3377,8 @@ merge(Compressor.prototype, { || cdr instanceof AST_PropAccess || cdr instanceof AST_Unary && !unary_side_effects(cdr.operator)) { field = "expression"; + } else if (cdr instanceof AST_Conditional) { + field = "condition"; } else { expressions[++i] = expressions[j]; break; diff --git a/test/compress/pure_getters.js b/test/compress/pure_getters.js index 81a96b73..2e7e17dc 100644 --- a/test/compress/pure_getters.js +++ b/test/compress/pure_getters.js @@ -241,3 +241,97 @@ issue_2110_2: { } expect_stdout: "function" } + +set_immutable_1: { + options = { + collapse_vars: true, + evaluate: true, + pure_getters: "strict", + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var a = 1; + a.foo += ""; + if (a.foo) console.log("FAIL"); + else console.log("PASS"); + } + expect: { + 1..foo += ""; + if (1..foo) console.log("FAIL"); + else console.log("PASS"); + } + expect_stdout: "PASS" +} + +set_immutable_2: { + options = { + cascade: true, + conditionals: true, + pure_getters: "strict", + reduce_vars: true, + sequences: true, + side_effects: true, + toplevel: true, + } + input: { + var a = 1; + a.foo += ""; + if (a.foo) console.log("FAIL"); + else console.log("PASS"); + } + expect: { + var a = 1; + a.foo += "", a.foo ? console.log("FAIL") : console.log("PASS"); + } + expect_stdout: "PASS" +} + +set_mutable_1: { + options = { + collapse_vars: true, + evaluate: true, + pure_getters: "strict", + reduce_vars: true, + unused: true, + } + input: { + !function a() { + a.foo += ""; + if (a.foo) console.log("PASS"); + else console.log("FAIL"); + }(); + } + expect: { + !function a() { + if (a.foo += "") console.log("PASS"); + else console.log("FAIL"); + }(); + } + expect_stdout: "PASS" +} + +set_mutable_2: { + options = { + cascade: true, + conditionals: true, + pure_getters: "strict", + reduce_vars: true, + sequences: true, + side_effects: true, + } + input: { + !function a() { + a.foo += ""; + if (a.foo) console.log("PASS"); + else console.log("FAIL"); + }(); + } + expect: { + !function a() { + (a.foo += "") ? console.log("PASS") : console.log("FAIL"); + }(); + } + expect_stdout: "PASS" +} From 285401ced889dcbc24c7431ae299bfbe328c5df5 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 25 Jun 2017 14:21:48 +0800 Subject: [PATCH 8/9] more tests for #2158 (#2160) --- test/compress/pure_getters.js | 50 +++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/test/compress/pure_getters.js b/test/compress/pure_getters.js index 2e7e17dc..dc56e19d 100644 --- a/test/compress/pure_getters.js +++ b/test/compress/pure_getters.js @@ -288,6 +288,56 @@ set_immutable_2: { expect_stdout: "PASS" } +set_immutable_3: { + options = { + collapse_vars: true, + evaluate: true, + pure_getters: "strict", + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + "use strict"; + var a = 1; + a.foo += ""; + if (a.foo) console.log("FAIL"); + else console.log("PASS"); + } + expect: { + "use strict"; + 1..foo += ""; + if (1..foo) console.log("FAIL"); + else console.log("PASS"); + } + expect_stdout: true +} + +set_immutable_4: { + options = { + cascade: true, + conditionals: true, + pure_getters: "strict", + reduce_vars: true, + sequences: true, + side_effects: true, + toplevel: true, + } + input: { + "use strict"; + var a = 1; + a.foo += ""; + if (a.foo) console.log("FAIL"); + else console.log("PASS"); + } + expect: { + "use strict"; + var a = 1; + a.foo += "", a.foo ? console.log("FAIL") : console.log("PASS"); + } + expect_stdout: true +} + set_mutable_1: { options = { collapse_vars: true, From 8b4dcd8f3e673209cd56ee44cc0292b4bcf60559 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 25 Jun 2017 15:05:05 +0800 Subject: [PATCH 9/9] v3.0.20 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2fd3ad70..47e71554 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.19", + "version": "3.0.20", "engines": { "node": ">=0.8.0" },