Compare commits

..

7 Commits

Author SHA1 Message Date
Alex Lam S.L
40ceddb48a v2.8.4 2017-03-02 00:24:49 +08:00
Alex Lam S.L
7aa69117e1 fix corner cases in reduce_vars (#1524)
Avoid variable substitution in the following cases:
- use of variable before declaration
- declaration within conditional code blocks
- declaration within loop body

fixes #1518
fixes #1525
2017-03-02 00:20:53 +08:00
Alex Lam S.L
bff7ad67bb v2.8.3 2017-03-01 15:28:46 +08:00
Alex Lam S.L
c2334baa48 fix crash on missing props to string_template() (#1523)
Patched up `make_node()` without `orig`.

There may be other cases where `start` could be missing, so make it print "undefined" instead of crashing.

fixes #1518
2017-03-01 15:25:26 +08:00
Alex Lam S.L
fb2b6c7c6f v2.8.2 2017-03-01 04:46:12 +08:00
Alex Lam S.L
f5cbe19b75 invert reduce_vars tracking flag (#1519)
Modules like webpack and grunt-contrib-uglify still uses `ast.transform(compressor)` before `Compressor.compress(ast)` was introduced.

Workaround this compatibility issue by deactivating `reduce_vars` in such case.

Also fix use case with omitted `options` when calling `Compressor()`.

fixes #1516
2017-03-01 04:12:10 +08:00
Alex Lam S.L
b34fa11a13 fix evaluate on object getter & setter (#1515) 2017-03-01 02:03:47 +08:00
7 changed files with 261 additions and 35 deletions

View File

@@ -621,7 +621,7 @@ function uglify(ast, options, mangle) {
// Compression // Compression
uAST.figure_out_scope(); uAST.figure_out_scope();
uAST = uAST.transform(UglifyJS.Compressor(options)); uAST = UglifyJS.Compressor(options).compress(uAST);
// Mangling (optional) // Mangling (optional)
if (mangle) { if (mangle) {
@@ -865,7 +865,7 @@ toplevel.figure_out_scope()
Like this: Like this:
```javascript ```javascript
var compressor = UglifyJS.Compressor(options); var compressor = UglifyJS.Compressor(options);
var compressed_ast = toplevel.transform(compressor); var compressed_ast = compressor.compress(toplevel);
``` ```
The `options` can be missing. Available options are discussed above in The `options` can be missing. Available options are discussed above in

View File

@@ -61,7 +61,7 @@ function Compressor(options, false_by_default) {
booleans : !false_by_default, booleans : !false_by_default,
loops : !false_by_default, loops : !false_by_default,
unused : !false_by_default, unused : !false_by_default,
toplevel : !!options["top_retain"], toplevel : !!(options && options["top_retain"]),
top_retain : null, top_retain : null,
hoist_funs : !false_by_default, hoist_funs : !false_by_default,
keep_fargs : true, keep_fargs : true,
@@ -70,7 +70,7 @@ function Compressor(options, false_by_default) {
if_return : !false_by_default, if_return : !false_by_default,
join_vars : !false_by_default, join_vars : !false_by_default,
collapse_vars : !false_by_default, collapse_vars : !false_by_default,
reduce_vars : false, reduce_vars : !false_by_default,
cascade : !false_by_default, cascade : !false_by_default,
side_effects : !false_by_default, side_effects : !false_by_default,
pure_getters : false, pure_getters : false,
@@ -179,38 +179,105 @@ merge(Compressor.prototype, {
AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan){ AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan){
var reduce_vars = rescan && compressor.option("reduce_vars"); var reduce_vars = rescan && compressor.option("reduce_vars");
var unsafe = compressor.option("unsafe"); var safe_ids = [];
push();
var tw = new TreeWalker(function(node){ var tw = new TreeWalker(function(node){
if (!(node instanceof AST_Directive || node instanceof AST_Constant)) {
node._squeezed = false;
node._optimized = false;
}
if (reduce_vars) { if (reduce_vars) {
if (node instanceof AST_Toplevel) node.globals.each(reset_def); if (node instanceof AST_Toplevel) node.globals.each(reset_def);
if (node instanceof AST_Scope) node.variables.each(reset_def); if (node instanceof AST_Scope) node.variables.each(reset_def);
if (node instanceof AST_SymbolRef) { if (node instanceof AST_SymbolRef) {
var d = node.definition(); var d = node.definition();
d.references.push(node); d.references.push(node);
if (!d.modified && (d.orig.length > 1 || isModified(node, 0))) { if (!d.fixed || isModified(node, 0) || !is_safe(d)) {
d.modified = true; d.fixed = false;
} }
} }
if (node instanceof AST_Call && node.expression instanceof AST_Function) { if (node instanceof AST_VarDef) {
node.expression.argnames.forEach(function(arg, i) { var d = node.name.definition();
arg.definition().init = node.args[i] || make_node(AST_Undefined, node); if (d.fixed === undefined) {
d.fixed = node.value || make_node(AST_Undefined, node);
mark_as_safe(d);
} else {
d.fixed = false;
}
}
var iife;
if (node instanceof AST_Function
&& (iife = tw.parent()) instanceof AST_Call
&& iife.expression === node) {
node.argnames.forEach(function(arg, i) {
var d = arg.definition();
d.fixed = iife.args[i] || make_node(AST_Undefined, iife);
mark_as_safe(d);
}); });
} }
if (node instanceof AST_If || node instanceof AST_DWLoop) {
node.condition.walk(tw);
push();
node.body.walk(tw);
pop();
if (node.alternative) {
push();
node.alternative.walk(tw);
pop();
}
return true;
}
if (node instanceof AST_LabeledStatement) {
push();
node.body.walk(tw);
pop();
return true;
}
if (node instanceof AST_For) {
if (node.init) node.init.walk(tw);
push();
if (node.condition) node.condition.walk(tw);
node.body.walk(tw);
if (node.step) node.step.walk(tw);
pop();
return true;
}
if (node instanceof AST_ForIn) {
if (node.init instanceof AST_SymbolRef) {
node.init.definition().fixed = false;
}
node.object.walk(tw);
push();
node.body.walk(tw);
pop();
return true;
} }
if (!(node instanceof AST_Directive || node instanceof AST_Constant)) {
node._squeezed = false;
node._optimized = false;
} }
}); });
this.walk(tw); this.walk(tw);
function mark_as_safe(def) {
safe_ids[safe_ids.length - 1][def.id] = true;
}
function is_safe(def) {
for (var i = safe_ids.length, id = def.id; --i >= 0;) {
if (safe_ids[i][id]) return true;
}
}
function push() {
safe_ids.push(Object.create(null));
}
function pop() {
safe_ids.pop();
}
function reset_def(def) { function reset_def(def) {
def.modified = false; def.fixed = undefined;
def.references = []; def.references = [];
def.should_replace = undefined; def.should_replace = undefined;
if (unsafe && def.init) {
def.init._evaluated = undefined;
}
} }
function isModified(node, level) { function isModified(node, level) {
@@ -1148,11 +1215,14 @@ merge(Compressor.prototype, {
def(AST_Statement, function(){ def(AST_Statement, function(){
throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start)); throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
}); });
// XXX: AST_Accessor and AST_Function both inherit from AST_Scope,
// which itself inherits from AST_Statement; however, they aren't
// really statements. This could bite in other places too. :-(
// Wish JS had multiple inheritance.
def(AST_Accessor, function(){
throw def;
});
def(AST_Function, function(){ def(AST_Function, function(){
// XXX: AST_Function inherits from AST_Scope, which itself
// inherits from AST_Statement; however, an AST_Function
// isn't really a statement. This could byte in other
// places too. :-( Wish JS had multiple inheritance.
throw def; throw def;
}); });
function ev(node, compressor) { function ev(node, compressor) {
@@ -1180,7 +1250,9 @@ merge(Compressor.prototype, {
for (var i = 0, len = this.properties.length; i < len; i++) { for (var i = 0, len = this.properties.length; i < len; i++) {
var prop = this.properties[i]; var prop = this.properties[i];
var key = prop.key; var key = prop.key;
if (key instanceof AST_Node) { if (key instanceof AST_Symbol) {
key = key.name;
} else if (key instanceof AST_Node) {
key = ev(key, compressor); key = ev(key, compressor);
} }
if (typeof Object.prototype[key] === 'function') { if (typeof Object.prototype[key] === 'function') {
@@ -1258,14 +1330,14 @@ merge(Compressor.prototype, {
this._evaluating = true; this._evaluating = true;
try { try {
var d = this.definition(); var d = this.definition();
if (compressor.option("reduce_vars") && !d.modified && d.init) { if (compressor.option("reduce_vars") && d.fixed) {
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
if (d.init._evaluated === undefined) { if (!HOP(d.fixed, "_evaluated")) {
d.init._evaluated = ev(d.init, compressor); d.fixed._evaluated = ev(d.fixed, compressor);
} }
return d.init._evaluated; return d.fixed._evaluated;
} }
return ev(d.init, compressor); return ev(d.fixed, compressor);
} }
} finally { } finally {
this._evaluating = false; this._evaluating = false;
@@ -2189,7 +2261,7 @@ merge(Compressor.prototype, {
// here because they are only used in an equality comparison later on. // here because they are only used in an equality comparison later on.
self.condition = negated; self.condition = negated;
var tmp = self.body; var tmp = self.body;
self.body = self.alternative || make_node(AST_EmptyStatement); self.body = self.alternative || make_node(AST_EmptyStatement, self);
self.alternative = tmp; self.alternative = tmp;
} }
if (is_empty(self.body) && is_empty(self.alternative)) { if (is_empty(self.body) && is_empty(self.alternative)) {
@@ -2454,7 +2526,7 @@ merge(Compressor.prototype, {
case "Boolean": case "Boolean":
if (self.args.length == 0) return make_node(AST_False, self); if (self.args.length == 0) return make_node(AST_False, self);
if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, { if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
expression: make_node(AST_UnaryPrefix, null, { expression: make_node(AST_UnaryPrefix, self, {
expression: self.args[0], expression: self.args[0],
operator: "!" operator: "!"
}), }),
@@ -2850,7 +2922,7 @@ merge(Compressor.prototype, {
compressor.warn("Boolean && always false [{file}:{line},{col}]", self.start); compressor.warn("Boolean && always false [{file}:{line},{col}]", self.start);
return make_node(AST_Seq, self, { return make_node(AST_Seq, self, {
car: self.left, car: self.left,
cdr: make_node(AST_False) cdr: make_node(AST_False, self)
}).optimize(compressor); }).optimize(compressor);
} }
if (ll.length > 1 && ll[1]) { if (ll.length > 1 && ll[1]) {
@@ -3041,9 +3113,9 @@ merge(Compressor.prototype, {
} }
if (compressor.option("evaluate") && compressor.option("reduce_vars")) { if (compressor.option("evaluate") && compressor.option("reduce_vars")) {
var d = self.definition(); var d = self.definition();
if (!d.modified && d.init) { if (d.fixed) {
if (d.should_replace === undefined) { if (d.should_replace === undefined) {
var init = d.init.evaluate(compressor); var init = d.fixed.evaluate(compressor);
if (init.length > 1) { if (init.length > 1) {
var value = init[0].print_to_string().length; var value = init[0].print_to_string().length;
var name = d.name.length; var name = d.name.length;

View File

@@ -185,7 +185,7 @@ function push_uniq(array, el) {
function string_template(text, props) { function string_template(text, props) {
return text.replace(/\{(.+?)\}/g, function(str, p){ return text.replace(/\{(.+?)\}/g, function(str, p){
return props[p]; return props && props[p];
}); });
}; };

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": "2.8.1", "version": "2.8.4",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },

View File

@@ -337,6 +337,32 @@ unsafe_object_repeated: {
} }
} }
unsafe_object_accessor: {
options = {
evaluate: true,
reduce_vars: true,
unsafe: true,
}
input: {
function f() {
var a = {
get b() {},
set b() {}
};
return {a:a};
}
}
expect: {
function f() {
var a = {
get b() {},
set b() {}
};
return {a:a};
}
}
}
unsafe_function: { unsafe_function: {
options = { options = {
evaluate : true, evaluate : true,

View File

@@ -470,3 +470,122 @@ multi_def_2: {
var repeatLength = this.getBits(bitsLength) + bitsOffset; var repeatLength = this.getBits(bitsLength) + bitsOffset;
} }
} }
use_before_var: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
console.log(t);
var t = 1;
}
expect: {
console.log(t);
var t = 1;
}
}
inner_var_if: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
function f(){
return 0;
}
if (f())
var t = 1;
if (!t)
console.log(t);
}
expect: {
function f(){
return 0;
}
if (f())
var t = 1;
if (!t)
console.log(t);
}
}
inner_var_label: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
function f(){
return 1;
}
l: {
if (f()) break l;
var t = 1;
}
console.log(t);
}
expect: {
function f(){
return 1;
}
l: {
if (f()) break l;
var t = 1;
}
console.log(t);
}
}
inner_var_for: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
var a = 1;
x(a, b, d);
for (var b = 2, c = 3; x(a, b, c, d); x(a, b, c, d)) {
var d = 4, e = 5;
x(a, b, c, d, e);
}
x(a, b, c, d, e)
}
expect: {
var a = 1;
x(1, b, d);
for (var b = 2, c = 3; x(1, b, 3, d); x(1, b, 3, d)) {
var d = 4, e = 5;
x(1, b, 3, d, e);
}
x(1, b, 3, d, e);
}
}
inner_var_for_in: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
var a = 1, b = 2;
for (b in (function() {
return x(a, b, c);
})()) {
var c = 3, d = 4;
x(a, b, c, d);
}
x(a, b, c, d);
}
expect: {
var a = 1, b = 2;
for (b in (function() {
return x(1, b, c);
})()) {
var c = 3, d = 4;
x(1, b, c, d);
}
x(1, b, c, d);
}
}

View File

@@ -182,4 +182,13 @@ describe("minify", function() {
}); });
}); });
describe("Compressor", function() {
it("should be backward compatible with ast.transform(compressor)", function() {
var ast = Uglify.parse("function f(a){for(var i=0;i<a;i++)console.log(i)}");
ast.figure_out_scope();
ast = ast.transform(Uglify.Compressor());
assert.strictEqual(ast.print_to_string(), "function f(a){for(var i=0;i<a;i++)console.log(i)}");
});
})
}); });