Compare commits

..

15 Commits

Author SHA1 Message Date
Alex Lam S.L
604caa09e7 v3.3.12 2018-02-22 08:14:29 +00:00
Alex Lam S.L
29a71d3aae more tests for #2938 (#2940) 2018-02-21 04:19:42 +08:00
Alex Lam S.L
39a907bde3 workaround pure_getters=true when dropping unused assignments (#2939)
fixes #2938
2018-02-20 17:38:40 +08:00
Alex Lam S.L
70474310f3 improve unsafe evaluate of function (#2936)
Miscellaneous
- simplify `run_code()` hack
2018-02-19 18:47:02 +08:00
Alex Lam S.L
b5f0f4f3a1 reduce false positives from object literals (#2935) 2018-02-19 06:21:07 +08:00
Alex Lam S.L
2905fd625a reduce false positives from labels (#2934) 2018-02-19 03:55:33 +08:00
Alex Lam S.L
4facd94029 reduce false positives from noop (#2933) 2018-02-19 01:15:05 +08:00
Alex Lam S.L
4b5993ff15 fix crash in may_throw() (#2932)
fixes #2931
2018-02-18 21:51:27 +08:00
Alex Lam S.L
2351a672ea fix dead_code on exceptional return (#2930)
fixes #2929
2018-02-18 04:36:00 +08:00
Alex Lam S.L
4a528c469c reduce false positives from function.toString() (#2928) 2018-02-18 02:13:26 +08:00
Alex Lam S.L
82d1ef0242 fix unsafe evaluate of function property (#2927)
fixes #2926
2018-02-17 21:33:36 +08:00
Alex Lam S.L
7fdd2082a6 drop unused "class" definition IIFEs (#2923)
fixes #805
2018-02-17 05:11:31 +08:00
Alex Lam S.L
e529f54e90 reduce function-related false positives (#2925) 2018-02-17 04:35:03 +08:00
Alex Lam S.L
d626e9bf19 improve inline efficiency (#2924) 2018-02-17 02:37:13 +08:00
Alex Lam S.L
a2a9459684 fix unsafe evaluate of AST_Function (#2920)
fixes #2919
2018-02-16 17:21:46 +08:00
11 changed files with 413 additions and 64 deletions

View File

@@ -2004,7 +2004,7 @@ merge(Compressor.prototype, {
if (this.properties[i].value instanceof AST_Accessor) return true; if (this.properties[i].value instanceof AST_Accessor) return true;
return false; return false;
}); });
def(AST_Function, return_false); def(AST_Lambda, return_false);
def(AST_UnaryPostfix, return_false); def(AST_UnaryPostfix, return_false);
def(AST_UnaryPrefix, function() { def(AST_UnaryPrefix, function() {
return this.operator == "void"; return this.operator == "void";
@@ -2023,8 +2023,9 @@ merge(Compressor.prototype, {
}) })
def(AST_Dot, function(compressor) { def(AST_Dot, function(compressor) {
if (!is_strict(compressor)) return false; if (!is_strict(compressor)) return false;
if (this.expression instanceof AST_Function && this.property == "prototype") return false; var exp = this.expression;
return true; if (exp instanceof AST_SymbolRef) exp = exp.fixed_value();
return !(exp instanceof AST_Lambda && this.property == "prototype");
}); });
def(AST_Sequence, function(compressor) { def(AST_Sequence, function(compressor) {
return this.tail_node()._dot_throw(compressor); return this.tail_node()._dot_throw(compressor);
@@ -2231,6 +2232,7 @@ merge(Compressor.prototype, {
"slice", "slice",
].concat(object_fns), ].concat(object_fns),
Boolean: object_fns, Boolean: object_fns,
Function: object_fns,
Number: [ Number: [
"toExponential", "toExponential",
"toFixed", "toFixed",
@@ -2313,7 +2315,9 @@ merge(Compressor.prototype, {
AST_Node.DEFMETHOD("evaluate", function(compressor){ AST_Node.DEFMETHOD("evaluate", function(compressor){
if (!compressor.option("evaluate")) return this; if (!compressor.option("evaluate")) return this;
var val = this._eval(compressor, 1); var val = this._eval(compressor, 1);
return !val || val instanceof RegExp || typeof val != "object" ? val : this; if (!val || val instanceof RegExp) return val;
if (typeof val == "function" || typeof val == "object") return this;
return val;
}); });
var unaryPrefix = makePredicate("! ~ - + void"); var unaryPrefix = makePredicate("! ~ - + void");
AST_Node.DEFMETHOD("is_constant", function(){ AST_Node.DEFMETHOD("is_constant", function(){
@@ -2335,15 +2339,22 @@ merge(Compressor.prototype, {
def(AST_Constant, function(){ def(AST_Constant, function(){
return this.getValue(); return this.getValue();
}); });
def(AST_Function, function(compressor) {
if (compressor.option("unsafe")) {
var fn = function() {};
fn.node = this;
fn.toString = function() {
return "function(){}";
};
return fn;
}
return this;
});
def(AST_Array, function(compressor, depth) { def(AST_Array, function(compressor, depth) {
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
var elements = []; var elements = [];
for (var i = 0, len = this.elements.length; i < len; i++) { for (var i = 0, len = this.elements.length; i < len; i++) {
var element = this.elements[i]; var element = this.elements[i];
if (element instanceof AST_Function) {
elements.push(element);
continue;
}
var value = element._eval(compressor, depth); var value = element._eval(compressor, depth);
if (element === value) return this; if (element === value) return this;
elements.push(value); elements.push(value);
@@ -2513,6 +2524,14 @@ merge(Compressor.prototype, {
} else { } else {
val = exp._eval(compressor, depth + 1); val = exp._eval(compressor, depth + 1);
if (!val || val === exp || !HOP(val, key)) return this; if (!val || val === exp || !HOP(val, key)) return this;
if (typeof val == "function") switch (key) {
case "name":
return val.node.name ? val.node.name.name : "";
case "length":
return val.node.argnames.length;
default:
return this;
}
} }
return val[key]; return val[key];
} }
@@ -2844,6 +2863,9 @@ merge(Compressor.prototype, {
def(AST_ObjectProperty, function(compressor){ def(AST_ObjectProperty, function(compressor){
return this.value.may_throw(compressor); return this.value.may_throw(compressor);
}); });
def(AST_Return, function(compressor){
return this.value && this.value.may_throw(compressor);
});
def(AST_Sequence, function(compressor){ def(AST_Sequence, function(compressor){
return any(this.expressions, compressor); return any(this.expressions, compressor);
}); });
@@ -2863,8 +2885,7 @@ merge(Compressor.prototype, {
return !this.is_declared(compressor); return !this.is_declared(compressor);
}); });
def(AST_Try, function(compressor){ def(AST_Try, function(compressor){
return any(this.body, compressor) return this.bcatch ? this.bcatch.may_throw(compressor) : any(this.body, compressor)
|| this.bcatch && this.bcatch.may_throw(compressor)
|| this.bfinally && this.bfinally.may_throw(compressor); || this.bfinally && this.bfinally.may_throw(compressor);
}); });
def(AST_Unary, function(compressor){ def(AST_Unary, function(compressor){
@@ -3000,11 +3021,20 @@ merge(Compressor.prototype, {
if (self.uses_eval || self.uses_with) return; if (self.uses_eval || self.uses_with) return;
var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs; var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs;
var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars; var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars;
var assign_as_unused = /keep_assign/.test(compressor.option("unused")) ? return_false : function(node) { var assign_as_unused = /keep_assign/.test(compressor.option("unused")) ? return_false : function(node, props) {
var sym;
if (node instanceof AST_Assign && (node.write_only || node.operator == "=")) { if (node instanceof AST_Assign && (node.write_only || node.operator == "=")) {
return node.left; sym = node.left;
} else if (node instanceof AST_Unary && node.write_only) {
sym = node.expression;
} }
if (node instanceof AST_Unary && node.write_only) return node.expression; if (/strict/.test(compressor.option("pure_getters"))) {
while (sym instanceof AST_PropAccess && !sym.expression.may_throw_on_access(compressor)) {
if (sym instanceof AST_Sub) props.unshift(sym.property);
sym = sym.expression;
}
}
return sym;
}; };
var in_use = []; var in_use = [];
var in_use_ids = Object.create(null); // avoid expensive linear scans of in_use var in_use_ids = Object.create(null); // avoid expensive linear scans of in_use
@@ -3080,18 +3110,27 @@ merge(Compressor.prototype, {
function before(node, descend, in_list) { function before(node, descend, in_list) {
var parent = tt.parent(); var parent = tt.parent();
if (drop_vars) { if (drop_vars) {
var sym = assign_as_unused(node); var props = [], sym = assign_as_unused(node, props);
if (sym instanceof AST_SymbolRef) { if (sym instanceof AST_SymbolRef) {
var def = sym.definition(); var def = sym.definition();
var in_use = def.id in in_use_ids; var in_use = def.id in in_use_ids;
var value = null;
if (node instanceof AST_Assign) { if (node instanceof AST_Assign) {
if (!in_use || def.id in fixed_ids && fixed_ids[def.id] !== node) { if (!in_use || node.left === sym && def.id in fixed_ids && fixed_ids[def.id] !== node) {
return maintain_this_binding(parent, node, node.right.transform(tt)); value = node.right;
} }
} else if (!in_use) return make_node(AST_Number, node, { } else if (!in_use) {
value = make_node(AST_Number, node, {
value: 0 value: 0
}); });
} }
if (value) {
props.push(value);
return maintain_this_binding(parent, node, make_sequence(node, props.map(function(prop) {
return prop.transform(tt);
})));
}
}
} }
if (scope !== self) return; if (scope !== self) return;
if (node instanceof AST_Function if (node instanceof AST_Function
@@ -3268,12 +3307,15 @@ merge(Compressor.prototype, {
self.transform(tt); self.transform(tt);
function scan_ref_scoped(node, descend) { function scan_ref_scoped(node, descend) {
var node_def, sym = assign_as_unused(node); var node_def, props = [], sym = assign_as_unused(node, props);
if (sym instanceof AST_SymbolRef if (sym instanceof AST_SymbolRef
&& self.variables.get(sym.name) === (node_def = sym.definition())) { && self.variables.get(sym.name) === (node_def = sym.definition())) {
props.forEach(function(prop) {
prop.walk(tw);
});
if (node instanceof AST_Assign) { if (node instanceof AST_Assign) {
node.right.walk(tw); node.right.walk(tw);
if (!node_def.chained && node.left.fixed_value() === node.right) { if (node.left === sym && !node_def.chained && sym.fixed_value() === node.right) {
fixed_ids[node_def.id] = node; fixed_ids[node_def.id] = node;
} }
} }
@@ -4478,6 +4520,8 @@ merge(Compressor.prototype, {
})) { })) {
return false; return false;
} }
} else if (line instanceof AST_EmptyStatement) {
continue;
} else if (stat) { } else if (stat) {
return false; return false;
} else { } else {
@@ -5471,7 +5515,7 @@ merge(Compressor.prototype, {
node = parent; node = parent;
parent = compressor.parent(level++); parent = compressor.parent(level++);
if (parent instanceof AST_Exit) { if (parent instanceof AST_Exit) {
if (in_try(level, parent instanceof AST_Throw)) break; if (in_try(level, parent)) break;
if (is_reachable(def.scope, [ def ])) break; if (is_reachable(def.scope, [ def ])) break;
if (self.operator == "=") return self.right; if (self.operator == "=") return self.right;
def.fixed = false; def.fixed = false;
@@ -5505,13 +5549,17 @@ merge(Compressor.prototype, {
} }
return self; return self;
function in_try(level, no_catch) { function in_try(level, node) {
var right = self.right;
self.right = make_node(AST_Null, right);
var may_throw = node.may_throw(compressor);
self.right = right;
var scope = self.left.definition().scope; var scope = self.left.definition().scope;
var parent; var parent;
while ((parent = compressor.parent(level++)) !== scope) { while ((parent = compressor.parent(level++)) !== scope) {
if (parent instanceof AST_Try) { if (parent instanceof AST_Try) {
if (parent.bfinally) return true; if (parent.bfinally) return true;
if (no_catch && parent.bcatch) return true; if (may_throw && parent.bcatch) return true;
} }
} }
} }

View File

@@ -4,7 +4,7 @@
"homepage": "http://lisperator.net/uglifyjs", "homepage": "http://lisperator.net/uglifyjs",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)", "author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"version": "3.3.11", "version": "3.3.12",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },

View File

@@ -4619,3 +4619,49 @@ issue_2914_2: {
} }
expect_stdout: "0" expect_stdout: "0"
} }
issue_805: {
options = {
collapse_vars: true,
pure_getters: "strict",
reduce_vars: true,
}
input: {
function f() {
function Foo(){}
Foo.prototype = {};
Foo.prototype.bar = 42;
return Foo;
}
}
expect: {
function f() {
function Foo(){}
(Foo.prototype = {}).bar = 42;
return Foo;
}
}
}
issue_2931: {
options = {
collapse_vars: true,
unused: true,
}
input: {
console.log(function() {
var a = function() {
return;
}();
return a;
}());
}
expect: {
console.log(function() {
return function() {
return;
}();
}());
}
expect_stdout: "undefined"
}

View File

@@ -917,3 +917,28 @@ issue_2860_2: {
} }
expect_stdout: "1" expect_stdout: "1"
} }
issue_2929: {
options = {
dead_code: true,
}
input: {
console.log(function(a) {
try {
return null.p = a = 1;
} catch (e) {
return a ? "PASS" : "FAIL";
}
}());
}
expect: {
console.log(function(a) {
try {
return null.p = a = 1;
} catch (e) {
return a ? "PASS" : "FAIL";
}
}());
}
expect_stdout: "PASS"
}

View File

@@ -1719,3 +1719,69 @@ issue_2846: {
} }
expect_stdout: "0" expect_stdout: "0"
} }
issue_805_1: {
options = {
inline: true,
passes: 2,
pure_getters: "strict",
reduce_vars: true,
sequences: true,
side_effects: true,
unused: true,
}
input: {
(function(a) {
var unused = function() {};
unused.prototype[a()] = 42;
(unused.prototype.bar = function() {
console.log("bar");
})();
return unused;
})(function() {
console.log("foo");
return "foo";
});
}
expect: {
console.log("foo"),
console.log("bar");
}
expect_stdout: [
"foo",
"bar",
]
}
issue_805_2: {
options = {
inline: true,
passes: 2,
pure_getters: "strict",
reduce_vars: true,
sequences: true,
side_effects: true,
unused: true,
}
input: {
(function(a) {
function unused() {}
unused.prototype[a()] = 42;
(unused.prototype.bar = function() {
console.log("bar");
})();
return unused;
})(function() {
console.log("foo");
return "foo";
});
}
expect: {
console.log("foo"),
console.log("bar");
}
expect_stdout: [
"foo",
"bar",
]
}

View File

@@ -1489,3 +1489,49 @@ issue_2916_2: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_2919: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log([ function() {} ].toString());
}
expect: {
console.log("function(){}");
}
}
issue_2926_1: {
options = {
evaluate: true,
reduce_vars: true,
unsafe: true,
}
input: {
(function f(a) {
console.log(f.name.length, f.length);
})();
}
expect: {
(function f(a) {
console.log(1, 1);
})();
}
expect_stdout: "1 1"
}
issue_2926_2: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log(typeof function() {}.valueOf());
}
expect: {
console.log("function");
}
expect_stdout: "function"
}

View File

@@ -292,11 +292,12 @@ issue_2084: {
} }
expect: { expect: {
var c = 0; var c = 0;
!function(c) { !function() {
c = 1 + c, var c;
c = 1 + (c = -1),
c = 1 + (c = 0), c = 1 + (c = 0),
0 !== 23..toString() && (c = 1 + c); 0 !== 23..toString() && (c = 1 + c);
}(-1), }(),
console.log(c); console.log(c);
} }
expect_stdout: "0" expect_stdout: "0"
@@ -1051,11 +1052,9 @@ issue_2616: {
} }
expect: { expect: {
var c = "FAIL"; var c = "FAIL";
(function() {
!function(NaN) { !function(NaN) {
(true << NaN) - 0/0 || (c = "PASS"); (true << NaN) - 0/0 || (c = "PASS");
}([]); }([]);
})();
console.log(c); console.log(c);
} }
expect_stdout: "PASS" expect_stdout: "PASS"
@@ -1086,13 +1085,11 @@ issue_2620_1: {
} }
expect: { expect: {
var c = "FAIL"; var c = "FAIL";
(function() { !function(a) {
(function(a) {
if (function(a) { if (function(a) {
a && a(); a && a();
}(), a) c = "PASS"; }(), a) c = "PASS";
})(1); }(1),
})(),
console.log(c); console.log(c);
} }
expect_stdout: "PASS" expect_stdout: "PASS"
@@ -1160,8 +1157,7 @@ issue_2620_3: {
} }
expect: { expect: {
var c = "FAIL"; var c = "FAIL";
(function() { !function(a, NaN) {
(function(a, NaN) {
(function() { (function() {
switch (a) { switch (a) {
case a: case a:
@@ -1170,8 +1166,7 @@ issue_2620_3: {
break; break;
} }
})(); })();
})(NaN); }(NaN);
})();
console.log(c); console.log(c);
} }
expect_stdout: "PASS" expect_stdout: "PASS"
@@ -1341,11 +1336,9 @@ issue_2630_4: {
} }
expect: { expect: {
var x = 3, a = 1, b = 2; var x = 3, a = 1, b = 2;
(function() { !function() {
(function() {
while (--x >= 0 && void (b += ++a)); while (--x >= 0 && void (b += ++a));
})(); }();
})();
console.log(a); console.log(a);
} }
expect_stdout: "2" expect_stdout: "2"

View File

@@ -721,3 +721,112 @@ issue_2838: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_2938_1: {
options = {
pure_getters: true,
unused: true,
}
input: {
function f(a) {
a.b = "PASS";
}
var o = {};
f(o);
console.log(o.b);
}
expect: {
function f(a) {
a.b = "PASS";
}
var o = {};
f(o);
console.log(o.b);
}
expect_stdout: "PASS"
}
issue_2938_2: {
options = {
pure_getters: true,
toplevel: true,
unused: true,
}
input: {
var Parser = function Parser() {};
var p = Parser.prototype;
p.initialContext = function initialContext() {
console.log("PASS");
};
p.braceIsBlock = function() {};
(new Parser).initialContext();
}
expect: {
var Parser = function() {};
var p = Parser.prototype;
p.initialContext = function() {
console.log("PASS");
};
p.braceIsBlock = function() {};
(new Parser).initialContext();
}
expect_stdout: "PASS"
}
issue_2938_3: {
options = {
pure_getters: true,
side_effects: true,
unused: true,
}
input: {
function f(a) {
var unused = a.a;
a.b = "PASS";
a.c;
}
var o = {};
o.d;
f(o);
console.log(o.b);
}
expect: {
function f(a) {
a.b = "PASS";
}
var o = {};
f(o);
console.log(o.b);
}
expect_stdout: "PASS"
}
issue_2938_4: {
options = {
pure_getters: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var Parser = function Parser() {};
var p = Parser.prototype;
var unused = p.x;
p.initialContext = function initialContext() {
p.y;
console.log("PASS");
};
p.braceIsBlock = function() {};
(new Parser).initialContext();
}
expect: {
var Parser = function() {};
var p = Parser.prototype;
p.initialContext = function() {
console.log("PASS");
};
p.braceIsBlock = function() {};
(new Parser).initialContext();
}
expect_stdout: "PASS"
}

View File

@@ -5527,3 +5527,21 @@ issue_2869: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_2919: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unsafe: true,
unused: true,
}
input: {
var arr = [ function() {} ];
console.log(typeof arr[0]);
}
expect: {
console.log("function");
}
expect_stdout: "function"
}

View File

@@ -226,7 +226,7 @@ describe("minify", function() {
content: "inline" content: "inline"
} }
}); });
assert.strictEqual(result.code, "var bar=function(){return function(bar){return bar}}();"); assert.strictEqual(result.code, "var bar=function(bar){return bar};");
assert.strictEqual(warnings.length, 1); assert.strictEqual(warnings.length, 1);
assert.strictEqual(warnings[0], "inline source map not found"); assert.strictEqual(warnings[0], "inline source map not found");
} finally { } finally {

View File

@@ -24,13 +24,12 @@ function strip_func_ids(text) {
var FUNC_TOSTRING = [ var FUNC_TOSTRING = [
"[ Array, Boolean, Error, Function, Number, Object, RegExp, String].forEach(function(f) {", "[ Array, Boolean, Error, Function, Number, Object, RegExp, String].forEach(function(f) {",
" f.toString = Function.prototype.toString;", " f.toString = Function.prototype.toString;",
" f.valueOf = Function.prototype.valueOf;",
"});", "});",
"Function.prototype.toString = Function.prototype.valueOf = function() {", "Function.prototype.toString = function() {",
" var id = 100000;", " var id = 100000;",
" return function() {", " return function() {",
" var n = this.name;", " var n = this.name;",
' if (!/^F[0-9]{6}N$/.test(n)) {', " if (!/^F[0-9]{6}N$/.test(n)) {",
' n = "F" + ++id + "N";', ' n = "F" + ++id + "N";',
].concat(Object.getOwnPropertyDescriptor(Function.prototype, "name").configurable ? [ ].concat(Object.getOwnPropertyDescriptor(Function.prototype, "name").configurable ? [
' Object.defineProperty(this, "name", {', ' Object.defineProperty(this, "name", {',
@@ -40,10 +39,9 @@ var FUNC_TOSTRING = [
" });", " });",
] : [], [ ] : [], [
" }", " }",
' return "[Function: " + n + "]";', ' return "function(){}";',
" }", " };",
"}();", "}();",
'Object.defineProperty(Function.prototype, "valueOf", { enumerable: false });',
]).join("\n"); ]).join("\n");
exports.run_code = function(code) { exports.run_code = function(code) {
var stdout = ""; var stdout = "";