From 24aa07855bc608f29cca2a58a40af1988256b116 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 22 Oct 2017 20:10:13 +0800 Subject: [PATCH] safer `properties` transform (#2391) `{ a: x, b: y }.a` => `[ x, y ][0]` - `x` cannot be function containing `this` `[ x, y, z ][1]` => `(x, z, y)` - only if `z` is side-effect-free --- lib/compress.js | 100 +++++++++++++++++------ test/compress/collapse_vars.js | 67 --------------- test/compress/evaluate.js | 22 ++--- test/compress/functions.js | 2 +- test/compress/properties.js | 145 +++++++++++++++++++++++++++++++-- test/compress/reduce_vars.js | 4 +- 6 files changed, 223 insertions(+), 117 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 8bc0e262..eb0e2016 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -4452,20 +4452,56 @@ merge(Compressor.prototype, { }); OPT(AST_Sub, function(self, compressor){ - var prop = self.property; - if (prop instanceof AST_String && compressor.option("properties")) { - prop = prop.getValue(); - if (is_identifier_string(prop)) { - return make_node(AST_Dot, self, { - expression : self.expression, - property : prop - }).optimize(compressor); + if (compressor.option("properties")) { + var prop = self.property; + if (prop instanceof AST_String) { + prop = prop.getValue(); + if (is_identifier_string(prop)) { + return make_node(AST_Dot, self, { + expression : self.expression, + property : prop + }).optimize(compressor); + } + var v = parseFloat(prop); + if (!isNaN(v) && v.toString() == prop) { + self.property = make_node(AST_Number, self.property, { + value: v + }); + } } - var v = parseFloat(prop); - if (!isNaN(v) && v.toString() == prop) { - self.property = make_node(AST_Number, self.property, { - value: v - }); + if (prop instanceof AST_Number && self.expression instanceof AST_Array) { + prop = prop.getValue(); + var elements = self.expression.elements; + if (prop in elements) { + var flatten = true; + var values = []; + for (var i = elements.length; --i > prop;) { + var value = elements[i].drop_side_effect_free(compressor); + if (value) { + values.unshift(value); + if (flatten && value.has_side_effects(compressor)) flatten = false; + } + } + var retValue = elements[prop]; + retValue = retValue instanceof AST_Hole ? make_node(AST_Undefined, retValue) : retValue; + if (!flatten) values.unshift(retValue); + while (--i >= 0) { + var value = elements[i].drop_side_effect_free(compressor); + if (value) values.unshift(value); + else prop--; + } + if (flatten) { + values.push(retValue); + return make_sequence(self, values).optimize(compressor); + } else return make_node(AST_Sub, self, { + expression: make_node(AST_Array, self.expression, { + elements: values + }), + property: make_node(AST_Number, self.property, { + value: prop + }) + }); + } } } if (is_lhs(self, compressor.parent())) return self; @@ -4493,20 +4529,6 @@ merge(Compressor.prototype, { if (def) { return def.optimize(compressor); } - if (is_lhs(self, compressor.parent())) return self; - 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 === self.property) { - var value = values[i].value; - if (value instanceof AST_Function ? value.contains_this() : value.has_side_effects(compressor)) break; - var obj = self.expression.clone(); - obj.properties = obj.properties.slice(); - obj.properties.splice(i, 1); - return make_sequence(self, [ obj, value ]).optimize(compressor); - } - } - } if (compressor.option("unsafe_proto") && self.expression instanceof AST_Dot && self.expression.property == "prototype") { @@ -4529,6 +4551,30 @@ merge(Compressor.prototype, { break; } } + if (is_lhs(self, compressor.parent())) return self; + if (compressor.option("properties") && self.expression instanceof AST_Object) { + var props = self.expression.properties; + for (var i = props.length; --i >= 0;) { + var prop = props[i]; + if (prop.key === self.property) { + if (!all(props, function(prop) { + return prop instanceof AST_ObjectKeyVal; + })) break; + var value = prop.value; + if (value instanceof AST_Function && value.contains_this()) break; + return make_node(AST_Sub, self, { + expression: make_node(AST_Array, self.expression, { + elements: props.map(function(prop) { + return prop.value; + }) + }), + property: make_node(AST_Number, self, { + value: i + }) + }).optimize(compressor); + } + } + } var ev = self.evaluate(compressor); if (ev !== self) { ev = make_node_from_constant(ev, self).optimize(compressor); diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 52b2ddf8..1f702ad7 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -2534,73 +2534,6 @@ issue_2319_3: { expect_stdout: "true" } -prop_side_effects_1: { - options = { - collapse_vars: true, - evaluate: true, - pure_getters: "strict", - reduce_vars: true, - toplevel: true, - unsafe: true, - unused: true, - } - input: { - var C = 1; - console.log(C); - var obj = { - bar: function() { - return C + C; - } - }; - console.log(obj.bar()); - } - expect: { - console.log(1); - console.log({ - bar: function() { - return 2; - } - }.bar()); - } - expect_stdout: [ - "1", - "2", - ] -} - -prop_side_effects_2: { - options = { - collapse_vars: true, - evaluate: true, - inline: true, - passes: 2, - pure_getters: "strict", - reduce_vars: true, - side_effects: true, - toplevel: true, - unsafe: true, - unused: true, - } - input: { - var C = 1; - console.log(C); - var obj = { - bar: function() { - return C + C; - } - }; - console.log(obj.bar()); - } - expect: { - console.log(1); - console.log(2); - } - expect_stdout: [ - "1", - "2", - ] -} - issue_2365: { options = { collapse_vars: true, diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 5f5a4a93..fe9464bc 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -386,10 +386,10 @@ unsafe_object_accessor: { } } -unsafe_function: { +prop_function: { options = { - evaluate : true, - unsafe : true + evaluate: true, + properties: true, } input: { console.log( @@ -402,9 +402,9 @@ unsafe_function: { expect: { console.log( ({a:{b:1},b:function(){}}) + 1, - ({b:function(){}}, {b:1}) + 1, - ({a:{b:1}}, function(){}) + 1, - ({b:function(){}}, {b:1}).b + 1 + ({b:1}) + 1, + function(){} + 1, + 2 ); } expect_stdout: true @@ -630,10 +630,10 @@ unsafe_string_bad_index: { expect_stdout: true } -unsafe_prototype_function: { +prototype_function: { options = { - evaluate : true, - unsafe : true + evaluate: true, + properties: true, } input: { var a = ({valueOf: 0}) < 1; @@ -652,8 +652,8 @@ unsafe_prototype_function: { var d = ({toString: 0}) + ""; var e = (({valueOf: 0}) + "")[2]; var f = (({toString: 0}) + "")[2]; - var g = ({}, 0)(); - var h = ({}, 0)(); + var g = 0(); + var h = 0(); } } diff --git a/test/compress/functions.js b/test/compress/functions.js index 6c82557d..febf81c1 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -153,10 +153,10 @@ function_returning_constant_literal: { options = { inline: true, passes: 2, + properties: true, reduce_vars: true, side_effects: true, toplevel: true, - unsafe: true, unused: true, } input: { diff --git a/test/compress/properties.js b/test/compress/properties.js index 496a43ca..f435d371 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -677,8 +677,8 @@ accessor_this: { issue_2208_1: { options = { inline: true, + properties: true, side_effects: true, - unsafe: true, } input: { console.log({ @@ -696,8 +696,8 @@ issue_2208_1: { issue_2208_2: { options = { inline: true, + properties: true, side_effects: true, - unsafe: true, } input: { console.log({ @@ -721,8 +721,8 @@ issue_2208_2: { issue_2208_3: { options = { inline: true, + properties: true, side_effects: true, - unsafe: true, } input: { a = 42; @@ -746,8 +746,8 @@ issue_2208_3: { issue_2208_4: { options = { inline: true, + properties: true, side_effects: true, - unsafe: true, } input: { function foo() {} @@ -770,8 +770,8 @@ issue_2208_4: { issue_2208_5: { options = { inline: true, + properties: true, side_effects: true, - unsafe: true, } input: { console.log({ @@ -808,7 +808,7 @@ issue_2256: { lhs_prop_1: { options = { evaluate: true, - unsafe: true, + properties: true, } input: { console.log(++{ @@ -827,9 +827,9 @@ lhs_prop_2: { options = { evaluate: true, inline: true, + properties: true, reduce_vars: true, side_effects: true, - unsafe: true, unused: true, } input: { @@ -844,7 +844,7 @@ lhs_prop_2: { literal_duplicate_key_side_effects: { options = { - unsafe: true, + properties: true, } input: { console.log({ @@ -852,11 +852,138 @@ literal_duplicate_key_side_effects: { a: console.log ? "PASS" : "FAIL" }.a); } + expect: { + console.log(console.log ? "PASS" : "FAIL"); + } + expect_stdout: "PASS" +} + +prop_side_effects_1: { + options = { + evaluate: true, + inline: true, + properties: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var C = 1; + console.log(C); + var obj = { + bar: function() { + return C + C; + } + }; + console.log(obj.bar()); + } + expect: { + console.log(1); + var obj = { + bar: function() { + return 2; + } + }; + console.log(obj.bar()); + } + expect_stdout: [ + "1", + "2", + ] +} + +prop_side_effects_2: { + options = { + evaluate: true, + inline: true, + passes: 2, + properties: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var C = 1; + console.log(C); + var obj = { + bar: function() { + return C + C; + } + }; + console.log(obj.bar()); + } + expect: { + console.log(1); + console.log(2); + } + expect_stdout: [ + "1", + "2", + ] +} + +accessor_1: { + options = { + properties: true, + } + input: { + console.log({ + a: "FAIL", + get a() { + return "PASS"; + } + }.a); + } expect: { console.log({ a: "FAIL", - a: console.log ? "PASS" : "FAIL" + get a() { + return "PASS"; + } }.a); } expect_stdout: "PASS" + node_version: ">=4" +} + +accessor_2: { + options = { + properties: true, + } + input: { + console.log({ + get a() { + return "PASS"; + }, + set a(v) {}, + a: "FAIL" + }.a); + } + expect: { + console.log({ + get a() { + return "PASS"; + }, + set a(v) {}, + a: "FAIL" + }.a); + } + expect_stdout: true +} + +array_hole: { + options = { + properties: true, + } + input: { + console.log( + [ 1, 2, , 3][1], + [ 1, 2, , 3][2], + [ 1, 2, , 3][3] + ); + } + expect: { + console.log(2, void 0, 3); + } + expect_stdout: "2 undefined 3" } diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 681dafd3..1274024f 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -2660,8 +2660,8 @@ obj_var_2: { evaluate: true, inline: true, passes: 2, + properties: true, reduce_vars: true, - side_effects: true, toplevel: true, unsafe: true, unused: true, @@ -2716,10 +2716,10 @@ obj_arg_2: { evaluate: true, inline: true, passes: 2, + properties: true, reduce_vars: true, side_effects: true, toplevel: true, - unsafe: true, unused: true, } input: { -- 2.34.1