Compare commits

...

38 Commits

Author SHA1 Message Date
Alex Lam S.L
87f8a484e6 v2.8.24 2017-05-12 15:47:02 +08:00
Alex Lam S.L
c736834aa4 Merge pull request #1921 from alexlamsl/v2.8.24 2017-05-12 14:58:35 +08:00
olsonpm
9a98513981 add documentation for side_effects & [#@]__PURE__ (#1925) 2017-05-12 12:55:07 +08:00
Alex Lam S.L
f631d6437a avoid arguments and eval in reduce_vars (#1924)
fixes #1922
2017-05-12 12:45:38 +08:00
Alex Lam S.L
aa7e8783f8 fix invalid transform on const (#1919)
- preserve (re)assignment to `const` for runtime error
- suppress `cascade` on `const`, as runtime behaviour is ill-defined
2017-05-12 05:04:28 +08:00
Alex Lam S.L
13e5e33448 document known issues with const (#1916) 2017-05-12 03:36:54 +08:00
kzc
487ae8e3be change harmony references to uglify-es in README (#1902) 2017-05-10 16:38:10 +08:00
Alex Lam S.L
5dfda6e212 v2.8.23 2017-05-07 04:31:54 +08:00
Alex Lam S.L
d08c772eb3 Merge pull request #1871 from alexlamsl/v2.8.23 2017-05-07 04:06:51 +08:00
Alex Lam S.L
90ed54401b fix test for #1865 (#1873) 2017-05-07 03:04:17 +08:00
Alex Lam S.L
d8106b6c63 fix label-related bugs (#1835)
- deep cloning of `AST_LabeledStatement`
- `L:do{...}while(false)`
- empty statement with label within block

extend `test/ufuzz.js`
- generate labels for blocks & loops
- generate for-in statements
- skip suspicious option search if `minify()` errs

fixes #1833
2017-05-07 00:16:30 +08:00
alexlamsl
dda4eb96e1 backport test scripts 2017-05-06 23:48:28 +08:00
Alex Lam S.L
7305ba0296 fix unsafe on evaluate of reduce_vars (#1870)
Determine if variables with non-constant values can escape and be modified.

fixes #1865
2017-05-06 23:40:19 +08:00
Alex Lam S.L
2c21dc5e8e fix unused on for-in statements (#1843)
Only need to avoid `var` within the initialisation block.

fixes #1841
2017-05-06 23:34:21 +08:00
Alex Lam S.L
d0faa471db fix unused on labeled for-loop (#1831)
fixes #1830
2017-05-06 23:31:22 +08:00
Alex Lam S.L
6ad823d1e8 fix reduce_vars within try-block (#1818)
Possible partial execution due to exceptions.
2017-05-06 23:28:07 +08:00
Alex Lam S.L
43ad4e9775 fix variable substitution (#1816)
- let `collapse_vars` take care of value containing any symbols
- improve overhead accounting
2017-05-06 23:26:54 +08:00
Alex Lam S.L
04b8964505 v2.8.22 2017-04-09 11:36:57 +08:00
Alex Lam S.L
d6fbc365e2 fix LHS cases for NaN & friends (#1804)
`Infinity = beyond` should not become `1/0 = beyond`
2017-04-09 03:18:14 +08:00
Alex Lam S.L
9a978843f5 enhance test/ufuzz.js (#1803)
- `-E` to report test cases with runtime errors
- favor returning expressions rather than empty return
- emit a newline upon fuzzer completion to not erase the iteration count

closes #1800
2017-04-09 01:36:38 +08:00
Alex Lam S.L
0479ff0c54 fix a couple of bugs in global_defs (#1802)
- `optimize()` substituted expression
- compute nested property string correctly

fixes #1801

Miscellaneous
- reset optimisation flags on all node types
2017-04-08 16:46:25 +08:00
Alex Lam S.L
cf72fe552f fix delete corner cases (#1799)
- assignment
- boolean
- conditional
- sequence
2017-04-08 14:25:28 +08:00
Alex Lam S.L
a1532eb076 extend ufuzz generator (#1783)
- property access
- property assignment
- allow bare expression within try-block
- normalise `Error` in `console.log()`
- generate more unary expressions
- add parenthesis to enforce precedence
- adjust variable reuse/creation
- add parameters to function declaration & expression
- add return expression
- add trivial arguments to function call
2017-04-07 18:47:30 +08:00
Alex Lam S.L
c2a1bceb77 fix pure_getters for chained property access (#1798) 2017-04-07 17:06:01 +08:00
Alex Lam S.L
e3c9c22c75 fix corner cases with delete (#1796)
`delete Infinity` returns `false` where as `delete (1/0)` returns `true`
2017-04-07 15:39:59 +08:00
Alex Lam S.L
0f4cd73dcc introduce "strict" to pure_getters (#1795) 2017-04-07 13:31:58 +08:00
Alex Lam S.L
281e882d27 fix reduce_vars on catch variable (#1794)
Improved catch handling in `figure_out_scope()` means special case treatment of IE8 is no longer valid in `reset_opt_flags()`.

Also fixed recursive assignment in variable definition.
2017-04-07 12:32:56 +08:00
Alex Lam S.L
cc6aa3e5ac fix incorrect context in variable substitution (#1791)
`AST_Node.optimize()` is context-aware, so don't cache its results to be used elsewhere.

Also fixed a few cases of AST corruption and beef up safety of `pure_getters`.
2017-04-07 03:42:17 +08:00
Alex Lam S.L
e869779a98 enable inline_script by default (#1793) 2017-04-07 00:45:51 +08:00
Alex Lam S.L
06cdb74279 improve pure_getters (#1786)
- property access to `null` & `undefined` always has side effects
- utilise `reduce_vars` to determine safe property access
- may-be cases treated as side effects unless `unsafe`
2017-04-06 11:18:59 +08:00
Alex Lam S.L
ff289b90a9 implement delayed resolution for reduce_vars (#1788)
Although it would be nice to enforce `AST_Node` cloning during transformation, that ship has sailed a long time ago.

We now get the assigned value when resolving `AST_SymbolRef` instead of `reset_opt_flags()`, which has the added advantage of improved compressor efficiency.

fixes #1787
2017-04-05 21:06:42 +08:00
Alex Lam S.L
9b6bc67c33 optimise do{...}while(false) (#1785)
- better heuristics to avoid issues like #1532
- fix `TreeWalker.loopcontrol_target()`
  - `continue` cannot refer to `switch` blocks
2017-04-04 23:48:22 +08:00
Alex Lam S.L
4b90dc1fdb remove --mangle-props from fuzzing (#1777)
The inherently unsafe nature makes this feature unsuitable to be tested this way.

fixes #1774
2017-04-04 16:24:16 +08:00
Alex Lam S.L
951770fc68 exclude mangling of special property names (#1779)
- `null`
- `true`
- `false`
- numeric literals
2017-04-04 03:50:19 +08:00
Alex Lam S.L
48b3fe9952 fix mangleProperties on identifiers (#1776)
- fix handling of "-Infinity"
- add test case for "-0"

reverts #1481
2017-04-03 23:17:47 +08:00
Alex Lam S.L
a400741868 workaround Node.js bugs (#1775)
Wrap test code in IIFE before passing to `vm`

fixes #1768
fixes #1771
2017-04-03 18:56:11 +08:00
Alex Lam S.L
59a4e56bc8 fix mangleProperties of undefined & Infinity (#1772)
`NaN` already works by the happy accident of `Number.NaN`

fixes #1770
2017-04-03 12:31:05 +08:00
Alex Lam S.L
1f1fccc45d extend test/ufuzz.js (#1769)
New expressions:
- property access
- array literal
- object literal

Miscellaneous:
- reduce execution timeout
- test `toplevel` and `mangleProperties`
2017-04-03 04:00:33 +08:00
25 changed files with 2317 additions and 436 deletions

View File

@@ -11,9 +11,10 @@ There's also an
Chrome and probably Safari). Chrome and probably Safari).
#### Note: #### Note:
- release versions of `uglify-js` only support ECMAScript 5 (ES5). If you wish to minify - `uglify-js` only supports ECMAScript 5 (ES5).
ES2015+ (ES6+) code then please use the [harmony](#harmony) development branch. - Support for `const` is [present but incomplete](#support-for-const), and may not be
- Node 7 has a known performance regression and runs `uglify-js` twice as slow. transformed properly.
- Those wishing to minify ES2015+ (ES6+) should use the `npm` package [**uglify-es**](https://github.com/mishoo/UglifyJS2/tree/harmony).
Install Install
------- -------
@@ -29,12 +30,6 @@ From NPM for programmatic use:
npm install uglify-js npm install uglify-js
From Git:
git clone git://github.com/mishoo/UglifyJS2.git
cd UglifyJS2
npm link .
Usage Usage
----- -----
@@ -411,6 +406,8 @@ to set `true`; it's effectively a shortcut for `foo=true`).
- `pure_getters` -- the default is `false`. If you pass `true` for - `pure_getters` -- the default is `false`. If you pass `true` for
this, UglifyJS will assume that object property access this, UglifyJS will assume that object property access
(e.g. `foo.bar` or `foo["bar"]`) doesn't have any side effects. (e.g. `foo.bar` or `foo["bar"]`) doesn't have any side effects.
Specify `"strict"` to treat `foo.bar` as side-effect-free only when
`foo` is certain to not throw, i.e. not `null` or `undefined`.
- `pure_funcs` -- default `null`. You can pass an array of names and - `pure_funcs` -- default `null`. You can pass an array of names and
UglifyJS will assume that those functions do not produce side UglifyJS will assume that those functions do not produce side
@@ -446,6 +443,11 @@ to set `true`; it's effectively a shortcut for `foo=true`).
- `keep_infinity` -- default `false`. Pass `true` to prevent `Infinity` from - `keep_infinity` -- default `false`. Pass `true` to prevent `Infinity` from
being compressed into `1/0`, which may cause performance issues on Chrome. being compressed into `1/0`, which may cause performance issues on Chrome.
- `side_effects` -- default `false`. Pass `true` to potentially drop functions
marked as "pure". (A function is marked as "pure" via the comment annotation
`/* @__PURE__ */` or `/* #__PURE__ */`)
### The `unsafe` option ### The `unsafe` option
It enables some transformations that *might* break code logic in certain It enables some transformations that *might* break code logic in certain
@@ -981,19 +983,9 @@ The `source_map_options` (optional) can contain the following properties:
[compressor]: http://lisperator.net/uglifyjs/compress [compressor]: http://lisperator.net/uglifyjs/compress
[parser]: http://lisperator.net/uglifyjs/parser [parser]: http://lisperator.net/uglifyjs/parser
#### Harmony #### Support for `const`
If you wish to use the experimental [harmony](https://github.com/mishoo/UglifyJS2/commits/harmony) `const` in `uglify-js@2.x` has function scope and as such behaves much like
branch to minify ES2015+ (ES6+) code please use the following in your `package.json` file: `var` - unlike `const` in ES2015 (ES6) which has block scope. It is recommended
to avoid using `const` for this reason as it will have undefined behavior when
``` run on an ES2015 compatible browser.
"uglify-js": "git+https://github.com/mishoo/UglifyJS2.git#harmony"
```
or to directly install the experimental harmony version of uglify:
```
npm install --save-dev uglify-js@github:mishoo/UglifyJS2#harmony
```
See [#448](https://github.com/mishoo/UglifyJS2/issues/448) for additional details.

View File

@@ -214,12 +214,13 @@ var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", {
clone: function(deep) { clone: function(deep) {
var node = this._clone(deep); var node = this._clone(deep);
if (deep) { if (deep) {
var refs = node.label.references; var label = node.label;
var label = this.label; var def = this.label;
node.walk(new TreeWalker(function(node) { node.walk(new TreeWalker(function(node) {
if (node instanceof AST_LoopControl if (node instanceof AST_LoopControl
&& node.label && node.label.thedef === label) { && node.label && node.label.thedef === def) {
refs.push(node); node.label.thedef = label;
label.references.push(node);
} }
})); }));
} }
@@ -1035,16 +1036,16 @@ TreeWalker.prototype = {
self = p; self = p;
} }
}, },
loopcontrol_target: function(label) { loopcontrol_target: function(node) {
var stack = this.stack; var stack = this.stack;
if (label) for (var i = stack.length; --i >= 0;) { if (node.label) for (var i = stack.length; --i >= 0;) {
var x = stack[i]; var x = stack[i];
if (x instanceof AST_LabeledStatement && x.label.name == label.name) { if (x instanceof AST_LabeledStatement && x.label.name == node.label.name)
return x.body; return x.body;
}
} else for (var i = stack.length; --i >= 0;) { } else for (var i = stack.length; --i >= 0;) {
var x = stack[i]; var x = stack[i];
if (x instanceof AST_Switch || x instanceof AST_IterationStatement) if (x instanceof AST_IterationStatement
|| node instanceof AST_Break && x instanceof AST_Switch)
return x; return x;
} }
} }

View File

@@ -71,7 +71,7 @@ function Compressor(options, false_by_default) {
negate_iife : !false_by_default, negate_iife : !false_by_default,
passes : 1, passes : 1,
properties : !false_by_default, properties : !false_by_default,
pure_getters : false, pure_getters : !false_by_default && "strict",
pure_funcs : null, pure_funcs : null,
reduce_vars : !false_by_default, reduce_vars : !false_by_default,
screw_ie8 : true, screw_ie8 : true,
@@ -251,9 +251,7 @@ 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 toplevel = compressor.option("toplevel"); var toplevel = compressor.option("toplevel");
var ie8 = !compressor.option("screw_ie8"); var safe_ids = Object.create(null);
var safe_ids = [];
push();
var suppressor = new TreeWalker(function(node) { var suppressor = new TreeWalker(function(node) {
if (node instanceof AST_Symbol) { if (node instanceof AST_Symbol) {
var d = node.definition(); var d = node.definition();
@@ -262,10 +260,8 @@ merge(Compressor.prototype, {
} }
}); });
var tw = new TreeWalker(function(node, descend){ var tw = new TreeWalker(function(node, descend){
if (!(node instanceof AST_Directive || node instanceof AST_Constant)) { node._squeezed = false;
node._squeezed = false; node._optimized = 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);
@@ -273,18 +269,35 @@ merge(Compressor.prototype, {
var d = node.definition(); var d = node.definition();
d.references.push(node); d.references.push(node);
if (d.fixed === undefined || !is_safe(d) if (d.fixed === undefined || !is_safe(d)
|| is_modified(node, 0, d.fixed instanceof AST_Lambda)) { || is_modified(node, 0, node.fixed_value() instanceof AST_Lambda)) {
d.fixed = false; d.fixed = false;
} else {
var parent = tw.parent();
if (parent instanceof AST_Assign && parent.operator == "=" && node === parent.right
|| parent instanceof AST_Call && node !== parent.expression
|| parent instanceof AST_Return && node === parent.value && node.scope !== d.scope
|| parent instanceof AST_VarDef && node === parent.value) {
d.escaped = true;
}
} }
} }
if (ie8 && node instanceof AST_SymbolCatch) { if (node instanceof AST_SymbolCatch) {
node.definition().fixed = false; node.definition().fixed = false;
} }
if (node instanceof AST_VarDef) { if (node instanceof AST_VarDef) {
var d = node.name.definition(); var d = node.name.definition();
if (d.fixed == null) { if (d.fixed == null) {
d.fixed = node.value; if (node.value) {
mark_as_safe(d); d.fixed = function() {
return node.value;
};
mark(d, false);
descend();
} else {
d.fixed = null;
}
mark(d, true);
return true;
} else if (node.value) { } else if (node.value) {
d.fixed = false; d.fixed = false;
} }
@@ -295,11 +308,10 @@ merge(Compressor.prototype, {
d.fixed = false; d.fixed = false;
} else { } else {
d.fixed = node; d.fixed = node;
mark_as_safe(d); mark(d, true);
} }
var save_ids = safe_ids; var save_ids = safe_ids;
safe_ids = []; safe_ids = Object.create(null);
push();
descend(); descend();
safe_ids = save_ids; safe_ids = save_ids;
return true; return true;
@@ -314,8 +326,14 @@ merge(Compressor.prototype, {
// So existing transformation rules can work on them. // So existing transformation rules can work on them.
node.argnames.forEach(function(arg, i) { node.argnames.forEach(function(arg, i) {
var d = arg.definition(); var d = arg.definition();
d.fixed = iife.args[i] || make_node(AST_Undefined, iife); if (!node.uses_arguments && d.fixed === undefined) {
mark_as_safe(d); d.fixed = function() {
return iife.args[i] || make_node(AST_Undefined, iife);
};
mark(d, true);
} else {
d.fixed = false;
}
}); });
} }
if (node instanceof AST_If || node instanceof AST_DWLoop) { if (node instanceof AST_If || node instanceof AST_DWLoop) {
@@ -353,7 +371,19 @@ merge(Compressor.prototype, {
pop(); pop();
return true; return true;
} }
if (node instanceof AST_Catch || node instanceof AST_SwitchBranch) { if (node instanceof AST_Try) {
push();
walk_body(node, tw);
pop();
if (node.bcatch) {
push();
node.bcatch.walk(tw);
pop();
}
if (node.bfinally) node.bfinally.walk(tw);
return true;
}
if (node instanceof AST_SwitchBranch) {
push(); push();
descend(); descend();
pop(); pop();
@@ -363,33 +393,34 @@ merge(Compressor.prototype, {
}); });
this.walk(tw); this.walk(tw);
function mark_as_safe(def) { function mark(def, safe) {
safe_ids[safe_ids.length - 1][def.id] = true; safe_ids[def.id] = safe;
} }
function is_safe(def) { function is_safe(def) {
for (var i = safe_ids.length, id = def.id; --i >= 0;) { if (safe_ids[def.id]) {
if (safe_ids[i][id]) { if (def.fixed == null) {
if (def.fixed == null) { var orig = def.orig[0];
var orig = def.orig[0]; if (orig instanceof AST_SymbolFunarg || orig.name == "arguments") return false;
if (orig instanceof AST_SymbolFunarg || orig.name == "arguments") return false; def.fixed = make_node(AST_Undefined, orig);
def.fixed = make_node(AST_Undefined, orig);
}
return true;
} }
return true;
} }
} }
function push() { function push() {
safe_ids.push(Object.create(null)); safe_ids = Object.create(safe_ids);
} }
function pop() { function pop() {
safe_ids.pop(); safe_ids = Object.getPrototypeOf(safe_ids);
} }
function reset_def(def) { function reset_def(def) {
if (toplevel || !def.global || def.orig[0] instanceof AST_SymbolConst) { def.escaped = false;
if (def.scope.uses_eval) {
def.fixed = false;
} else if (toplevel || !def.global || def.orig[0] instanceof AST_SymbolConst) {
def.fixed = undefined; def.fixed = undefined;
} else { } else {
def.fixed = false; def.fixed = false;
@@ -400,7 +431,7 @@ merge(Compressor.prototype, {
function is_modified(node, level, func) { function is_modified(node, level, func) {
var parent = tw.parent(level); var parent = tw.parent(level);
if (isLHS(node, parent) if (is_lhs(node, parent)
|| !func && parent instanceof AST_Call && parent.expression === node) { || !func && parent instanceof AST_Call && parent.expression === node) {
return true; return true;
} else if (parent instanceof AST_PropAccess && parent.expression === node) { } else if (parent instanceof AST_PropAccess && parent.expression === node) {
@@ -409,6 +440,20 @@ merge(Compressor.prototype, {
} }
}); });
AST_SymbolRef.DEFMETHOD("fixed_value", function() {
var fixed = this.definition().fixed;
if (!fixed || fixed instanceof AST_Node) return fixed;
return fixed();
});
function is_reference_const(ref) {
if (!(ref instanceof AST_SymbolRef)) return false;
var orig = ref.definition().orig;
for (var i = orig.length; --i >= 0;) {
if (orig[i] instanceof AST_SymbolConst) return true;
}
}
function find_variable(compressor, name) { function find_variable(compressor, name) {
var scope, i = 0; var scope, i = 0;
while (scope = compressor.parent(i++)) { while (scope = compressor.parent(i++)) {
@@ -469,15 +514,15 @@ merge(Compressor.prototype, {
// func(something) because that changes the meaning of // func(something) because that changes the meaning of
// the func (becomes lexical instead of global). // the func (becomes lexical instead of global).
function maintain_this_binding(parent, orig, val) { function maintain_this_binding(parent, orig, val) {
if (parent instanceof AST_Call && parent.expression === orig) { if (parent instanceof AST_UnaryPrefix && parent.operator == "delete"
if (val instanceof AST_PropAccess || val instanceof AST_SymbolRef && val.name === "eval") { || parent instanceof AST_Call && parent.expression === orig
return make_node(AST_Seq, orig, { && (val instanceof AST_PropAccess || val instanceof AST_SymbolRef && val.name == "eval")) {
car: make_node(AST_Number, orig, { return make_node(AST_Seq, orig, {
value: 0 car: make_node(AST_Number, orig, {
}), value: 0
cdr: val }),
}); cdr: val
} });
} }
return val; return val;
} }
@@ -687,7 +732,7 @@ merge(Compressor.prototype, {
return statements; return statements;
function is_lvalue(node, parent) { function is_lvalue(node, parent) {
return node instanceof AST_SymbolRef && isLHS(node, parent); return node instanceof AST_SymbolRef && is_lhs(node, parent);
} }
function replace_var(node, parent, is_constant) { function replace_var(node, parent, is_constant) {
if (is_lvalue(node, parent)) return node; if (is_lvalue(node, parent)) return node;
@@ -893,7 +938,7 @@ merge(Compressor.prototype, {
} }
var ab = aborts(stat.body); var ab = aborts(stat.body);
var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab.label) : null; var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab) : null;
if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda) if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
|| (ab instanceof AST_Continue && self === loop_body(lct)) || (ab instanceof AST_Continue && self === loop_body(lct))
|| (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) { || (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) {
@@ -915,7 +960,7 @@ merge(Compressor.prototype, {
} }
var ab = aborts(stat.alternative); var ab = aborts(stat.alternative);
var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab.label) : null; var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab) : null;
if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda) if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
|| (ab instanceof AST_Continue && self === loop_body(lct)) || (ab instanceof AST_Continue && self === loop_body(lct))
|| (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) { || (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) {
@@ -964,7 +1009,7 @@ merge(Compressor.prototype, {
extract_declarations_from_unreachable_code(compressor, stat, a); extract_declarations_from_unreachable_code(compressor, stat, a);
} else { } else {
if (stat instanceof AST_LoopControl) { if (stat instanceof AST_LoopControl) {
var lct = compressor.loopcontrol_target(stat.label); var lct = compressor.loopcontrol_target(stat);
if ((stat instanceof AST_Break if ((stat instanceof AST_Break
&& !(lct instanceof AST_IterationStatement) && !(lct instanceof AST_IterationStatement)
&& loop_body(lct) === self) || (stat instanceof AST_Continue && loop_body(lct) === self) || (stat instanceof AST_Continue
@@ -1150,6 +1195,61 @@ merge(Compressor.prototype, {
&& !node.expression.has_side_effects(compressor); && !node.expression.has_side_effects(compressor);
} }
// may_eq_null()
// returns true if this node may evaluate to null or undefined
(function(def) {
AST_Node.DEFMETHOD("may_eq_null", function(compressor) {
var pure_getters = compressor.option("pure_getters");
return !pure_getters || this._eq_null(pure_getters);
});
function is_strict(pure_getters) {
return /strict/.test(pure_getters);
}
def(AST_Node, is_strict);
def(AST_Null, return_true);
def(AST_Undefined, return_true);
def(AST_Constant, return_false);
def(AST_Array, return_false);
def(AST_Object, return_false);
def(AST_Function, return_false);
def(AST_UnaryPostfix, return_false);
def(AST_UnaryPrefix, function() {
return this.operator == "void";
});
def(AST_Binary, function(pure_getters) {
switch (this.operator) {
case "&&":
return this.left._eq_null(pure_getters);
case "||":
return this.left._eq_null(pure_getters)
&& this.right._eq_null(pure_getters);
default:
return false;
}
})
def(AST_Assign, function(pure_getters) {
return this.operator == "="
&& this.right._eq_null(pure_getters);
})
def(AST_Conditional, function(pure_getters) {
return this.consequent._eq_null(pure_getters)
|| this.alternative._eq_null(pure_getters);
})
def(AST_Seq, function(pure_getters) {
return this.cdr._eq_null(pure_getters);
});
def(AST_SymbolRef, function(pure_getters) {
if (this.is_undefined) return true;
if (!is_strict(pure_getters)) return false;
var fixed = this.fixed_value();
return !fixed || fixed._eq_null(pure_getters);
});
})(function(node, func) {
node.DEFMETHOD("_eq_null", func);
});
/* -----[ boolean/negation helpers ]----- */ /* -----[ boolean/negation helpers ]----- */
// methods to determine whether an expression has a boolean result type // methods to determine whether an expression has a boolean result type
@@ -1234,9 +1334,9 @@ merge(Compressor.prototype, {
var unary_side_effects = makePredicate("delete ++ --"); var unary_side_effects = makePredicate("delete ++ --");
function isLHS(node, parent) { function is_lhs(node, parent) {
return parent instanceof AST_Unary && unary_side_effects(parent.operator) if (parent instanceof AST_Unary && unary_side_effects(parent.operator)) return parent.expression;
|| parent instanceof AST_Assign && parent.left === node; if (parent instanceof AST_Assign && parent.left === node) return node;
} }
(function (def){ (function (def){
@@ -1249,7 +1349,7 @@ merge(Compressor.prototype, {
node = parent; node = parent;
parent = compressor.parent(level++); parent = compressor.parent(level++);
} while (parent instanceof AST_PropAccess && parent.expression === node); } while (parent instanceof AST_PropAccess && parent.expression === node);
if (isLHS(node, parent)) { if (is_lhs(node, parent)) {
compressor.warn('global_defs ' + this.print_to_string() + ' redefined [{file}:{line},{col}]', this.start); compressor.warn('global_defs ' + this.print_to_string() + ' redefined [{file}:{line},{col}]', this.start);
} else { } else {
return def; return def;
@@ -1279,7 +1379,7 @@ merge(Compressor.prototype, {
} }
def(AST_Node, noop); def(AST_Node, noop);
def(AST_Dot, function(compressor, suffix){ def(AST_Dot, function(compressor, suffix){
return this.expression._find_defs(compressor, suffix + "." + this.property); return this.expression._find_defs(compressor, "." + this.property + suffix);
}); });
def(AST_SymbolRef, function(compressor, suffix){ def(AST_SymbolRef, function(compressor, suffix){
if (!this.global()) return; if (!this.global()) return;
@@ -1484,23 +1584,20 @@ merge(Compressor.prototype, {
: ev(this.alternative, compressor); : ev(this.alternative, compressor);
}); });
def(AST_SymbolRef, function(compressor){ def(AST_SymbolRef, function(compressor){
if (this._evaluating) throw def; if (!compressor.option("reduce_vars") || this._evaluating) throw def;
this._evaluating = true; this._evaluating = true;
try { try {
var d = this.definition(); var fixed = this.fixed_value();
if (compressor.option("reduce_vars") && d.fixed) { if (!fixed) throw def;
if (compressor.option("unsafe")) { var value = ev(fixed, compressor);
if (!HOP(d.fixed, "_evaluated")) { if (!HOP(fixed, "_eval")) fixed._eval = function() {
d.fixed._evaluated = ev(d.fixed, compressor); return value;
} };
return d.fixed._evaluated; if (value && typeof value == "object" && this.definition().escaped) throw def;
} return value;
return ev(d.fixed, compressor);
}
} finally { } finally {
this._evaluating = false; this._evaluating = false;
} }
throw def;
}); });
def(AST_PropAccess, function(compressor){ def(AST_PropAccess, function(compressor){
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
@@ -1678,7 +1775,7 @@ merge(Compressor.prototype, {
|| this.expression.has_side_effects(compressor); || this.expression.has_side_effects(compressor);
}); });
def(AST_SymbolRef, function(compressor){ def(AST_SymbolRef, function(compressor){
return this.global() && this.undeclared(); return this.undeclared();
}); });
def(AST_Object, function(compressor){ def(AST_Object, function(compressor){
return any(this.properties, compressor); return any(this.properties, compressor);
@@ -1690,17 +1787,14 @@ merge(Compressor.prototype, {
return any(this.elements, compressor); return any(this.elements, compressor);
}); });
def(AST_Dot, function(compressor){ def(AST_Dot, function(compressor){
if (!compressor.option("pure_getters")) return true; return this.expression.may_eq_null(compressor)
return this.expression.has_side_effects(compressor); || this.expression.has_side_effects(compressor);
}); });
def(AST_Sub, function(compressor){ def(AST_Sub, function(compressor){
if (!compressor.option("pure_getters")) return true; return this.expression.may_eq_null(compressor)
return this.expression.has_side_effects(compressor) || this.expression.has_side_effects(compressor)
|| this.property.has_side_effects(compressor); || this.property.has_side_effects(compressor);
}); });
def(AST_PropAccess, function(compressor){
return !compressor.option("pure_getters");
});
def(AST_Seq, function(compressor){ def(AST_Seq, function(compressor){
return this.car.has_side_effects(compressor) return this.car.has_side_effects(compressor)
|| this.cdr.has_side_effects(compressor); || this.cdr.has_side_effects(compressor);
@@ -1746,7 +1840,7 @@ merge(Compressor.prototype, {
OPT(AST_LabeledStatement, function(self, compressor){ OPT(AST_LabeledStatement, function(self, compressor){
if (self.body instanceof AST_Break if (self.body instanceof AST_Break
&& compressor.loopcontrol_target(self.body.label) === self.body) { && compressor.loopcontrol_target(self.body) === self.body) {
return make_node(AST_EmptyStatement, self); return make_node(AST_EmptyStatement, self);
} }
return self.label.references.length == 0 ? self.body : self; return self.label.references.length == 0 ? self.body : self;
@@ -1829,6 +1923,7 @@ merge(Compressor.prototype, {
&& node instanceof AST_Assign && node instanceof AST_Assign
&& node.operator == "=" && node.operator == "="
&& node.left instanceof AST_SymbolRef && node.left instanceof AST_SymbolRef
&& !is_reference_const(node.left)
&& scope === self) { && scope === self) {
node.right.walk(tw); node.right.walk(tw);
return true; return true;
@@ -1918,7 +2013,7 @@ merge(Compressor.prototype, {
} }
return node; return node;
} }
if (drop_vars && node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) { if (drop_vars && node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn && tt.parent().init === node)) {
var def = node.definitions.filter(function(def){ var def = node.definitions.filter(function(def){
if (def.value) def.value = def.value.transform(tt); if (def.value) def.value = def.value.transform(tt);
var sym = def.name.definition(); var sym = def.name.definition();
@@ -1996,26 +2091,32 @@ merge(Compressor.prototype, {
return maintain_this_binding(tt.parent(), node, node.right.transform(tt)); return maintain_this_binding(tt.parent(), node, node.right.transform(tt));
} }
} }
// certain combination of unused name + side effect leads to:
// https://github.com/mishoo/UglifyJS2/issues/44
// https://github.com/mishoo/UglifyJS2/issues/1830
// that's an invalid AST.
// We fix it at this stage by moving the `var` outside the `for`.
if (node instanceof AST_For) { if (node instanceof AST_For) {
descend(node, this); descend(node, this);
if (node.init instanceof AST_BlockStatement) { if (node.init instanceof AST_BlockStatement) {
// certain combination of unused name + side effect leads to: var block = node.init;
// https://github.com/mishoo/UglifyJS2/issues/44 node.init = block.body.pop();
// that's an invalid AST. block.body.push(node);
// We fix it at this stage by moving the `var` outside the `for`. return in_list ? MAP.splice(block.body) : block;
var body = node.init.body.slice(0, -1);
node.init = node.init.body.slice(-1)[0].body;
body.push(node);
return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, {
body: body
});
} else if (is_empty(node.init)) { } else if (is_empty(node.init)) {
node.init = null; node.init = null;
return node;
} }
return node;
}
if (node instanceof AST_LabeledStatement && node.body instanceof AST_For) {
descend(node, this);
if (node.body instanceof AST_BlockStatement) {
var block = node.body;
node.body = block.body.pop();
block.body.push(node);
return in_list ? MAP.splice(block.body) : block;
}
return node;
} }
if (node instanceof AST_Scope && node !== self) if (node instanceof AST_Scope && node !== self)
return node; return node;
@@ -2264,11 +2365,11 @@ merge(Compressor.prototype, {
return values && AST_Seq.from_array(values); return values && AST_Seq.from_array(values);
}); });
def(AST_Dot, function(compressor, first_in_statement){ def(AST_Dot, function(compressor, first_in_statement){
if (!compressor.option("pure_getters")) return this; if (this.expression.may_eq_null(compressor)) return this;
return this.expression.drop_side_effect_free(compressor, first_in_statement); return this.expression.drop_side_effect_free(compressor, first_in_statement);
}); });
def(AST_Sub, function(compressor, first_in_statement){ def(AST_Sub, function(compressor, first_in_statement){
if (!compressor.option("pure_getters")) return this; if (this.expression.may_eq_null(compressor)) return this;
var expression = this.expression.drop_side_effect_free(compressor, first_in_statement); var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
if (!expression) return this.property.drop_side_effect_free(compressor, first_in_statement); if (!expression) return this.property.drop_side_effect_free(compressor, first_in_statement);
var property = this.property.drop_side_effect_free(compressor); var property = this.property.drop_side_effect_free(compressor);
@@ -2314,13 +2415,22 @@ merge(Compressor.prototype, {
return make_node(AST_For, self, { return make_node(AST_For, self, {
body: self.body body: self.body
}); });
} else if (compressor.option("dead_code") && self instanceof AST_While) { }
if (compressor.option("dead_code") && self instanceof AST_While) {
var a = []; var a = [];
extract_declarations_from_unreachable_code(compressor, self.body, a); extract_declarations_from_unreachable_code(compressor, self.body, a);
return make_node(AST_BlockStatement, self, { body: a }); return make_node(AST_BlockStatement, self, { body: a }).optimize(compressor);
} else { }
cond = make_node_from_constant(cond, self.condition).transform(compressor); if (self instanceof AST_Do) {
self.condition = best_of_expression(cond, self.condition); var has_loop_control = false;
var tw = new TreeWalker(function(node) {
if (node instanceof AST_Scope || has_loop_control) return true;
if (node instanceof AST_LoopControl && tw.loopcontrol_target(node) === self)
return has_loop_control = true;
});
var parent = compressor.parent();
(parent instanceof AST_LabeledStatement ? parent : self).walk(tw);
if (!has_loop_control) return self.body;
} }
} }
if (self instanceof AST_While) { if (self instanceof AST_While) {
@@ -2346,7 +2456,7 @@ merge(Compressor.prototype, {
var first = self.body instanceof AST_BlockStatement ? self.body.body[0] : self.body; var first = self.body instanceof AST_BlockStatement ? self.body.body[0] : self.body;
if (first instanceof AST_If) { if (first instanceof AST_If) {
if (first.body instanceof AST_Break if (first.body instanceof AST_Break
&& compressor.loopcontrol_target(first.body.label) === compressor.self()) { && compressor.loopcontrol_target(first.body) === compressor.self()) {
if (self.condition) { if (self.condition) {
self.condition = make_node(AST_Binary, self.condition, { self.condition = make_node(AST_Binary, self.condition, {
left: self.condition, left: self.condition,
@@ -2359,7 +2469,7 @@ merge(Compressor.prototype, {
drop_it(first.alternative); drop_it(first.alternative);
} }
else if (first.alternative instanceof AST_Break else if (first.alternative instanceof AST_Break
&& compressor.loopcontrol_target(first.alternative.label) === compressor.self()) { && compressor.loopcontrol_target(first.alternative) === compressor.self()) {
if (self.condition) { if (self.condition) {
self.condition = make_node(AST_Binary, self.condition, { self.condition = make_node(AST_Binary, self.condition, {
left: self.condition, left: self.condition,
@@ -2389,7 +2499,7 @@ merge(Compressor.prototype, {
})); }));
} }
extract_declarations_from_unreachable_code(compressor, self.body, a); extract_declarations_from_unreachable_code(compressor, self.body, a);
return make_node(AST_BlockStatement, self, { body: a }); return make_node(AST_BlockStatement, self, { body: a }).optimize(compressor);
} }
if (cond !== self.condition) { if (cond !== self.condition) {
cond = make_node_from_constant(cond, self.condition).transform(compressor); cond = make_node_from_constant(cond, self.condition).transform(compressor);
@@ -2590,7 +2700,7 @@ merge(Compressor.prototype, {
self.body = body; self.body = body;
while (branch = body[body.length - 1]) { while (branch = body[body.length - 1]) {
var stat = branch.body[branch.body.length - 1]; var stat = branch.body[branch.body.length - 1];
if (stat instanceof AST_Break && compressor.loopcontrol_target(stat.label) === self) if (stat instanceof AST_Break && compressor.loopcontrol_target(stat) === self)
branch.body.pop(); branch.body.pop();
if (branch.body.length || branch instanceof AST_Case if (branch.body.length || branch instanceof AST_Case
&& (default_branch || branch.expression.has_side_effects(compressor))) break; && (default_branch || branch.expression.has_side_effects(compressor))) break;
@@ -2609,7 +2719,7 @@ merge(Compressor.prototype, {
if (has_break if (has_break
|| node instanceof AST_Lambda || node instanceof AST_Lambda
|| node instanceof AST_SimpleStatement) return true; || node instanceof AST_SimpleStatement) return true;
if (node instanceof AST_Break && tw.loopcontrol_target(node.label) === self) if (node instanceof AST_Break && tw.loopcontrol_target(node) === self)
has_break = true; has_break = true;
}); });
self.walk(tw); self.walk(tw);
@@ -2641,9 +2751,9 @@ merge(Compressor.prototype, {
var body = []; var body = [];
if (self.bcatch) extract_declarations_from_unreachable_code(compressor, self.bcatch, body); if (self.bcatch) extract_declarations_from_unreachable_code(compressor, self.bcatch, body);
if (self.bfinally) body = body.concat(self.bfinally.body); if (self.bfinally) body = body.concat(self.bfinally.body);
return body.length > 0 ? make_node(AST_BlockStatement, self, { return make_node(AST_BlockStatement, self, {
body: body body: body
}).optimize(compressor) : make_node(AST_EmptyStatement, self); }).optimize(compressor);
} }
return self; return self;
}); });
@@ -2681,11 +2791,12 @@ merge(Compressor.prototype, {
if (compressor.option("reduce_vars") if (compressor.option("reduce_vars")
&& exp instanceof AST_SymbolRef) { && exp instanceof AST_SymbolRef) {
var def = exp.definition(); var def = exp.definition();
if (def.fixed instanceof AST_Defun) { var fixed = exp.fixed_value();
def.fixed = make_node(AST_Function, def.fixed, def.fixed).clone(true); if (fixed instanceof AST_Defun) {
def.fixed = fixed = make_node(AST_Function, fixed, fixed).clone(true);
} }
if (def.fixed instanceof AST_Function) { if (fixed instanceof AST_Function) {
exp = def.fixed; exp = fixed;
if (compressor.option("unused") if (compressor.option("unused")
&& def.references.length == 1 && def.references.length == 1
&& !(def.scope.uses_arguments && !(def.scope.uses_arguments
@@ -2970,7 +3081,8 @@ merge(Compressor.prototype, {
} }
if (left if (left
&& !(left instanceof AST_SymbolRef && !(left instanceof AST_SymbolRef
&& left.definition().orig[0] instanceof AST_SymbolLambda)) { && (left.definition().orig[0] instanceof AST_SymbolLambda
|| is_reference_const(left)))) {
var parent, field; var parent, field;
var cdr = self.cdr; var cdr = self.cdr;
while (true) { while (true) {
@@ -3015,8 +3127,9 @@ merge(Compressor.prototype, {
if (this.expression instanceof AST_Seq) { if (this.expression instanceof AST_Seq) {
var seq = this.expression; var seq = this.expression;
var x = seq.to_array(); var x = seq.to_array();
this.expression = x.pop(); var e = this.clone();
x.push(this); e.expression = x.pop();
x.push(e);
seq = AST_Seq.from_array(x).transform(compressor); seq = AST_Seq.from_array(x).transform(compressor);
return seq; return seq;
} }
@@ -3029,11 +3142,27 @@ merge(Compressor.prototype, {
}); });
OPT(AST_UnaryPrefix, function(self, compressor){ OPT(AST_UnaryPrefix, function(self, compressor){
var e = self.expression;
if (self.operator == "delete"
&& !(e instanceof AST_SymbolRef
|| e instanceof AST_PropAccess
|| e instanceof AST_NaN
|| e instanceof AST_Infinity
|| e instanceof AST_Undefined)) {
if (e instanceof AST_Seq) {
e = e.to_array();
e.push(make_node(AST_True, self));
return AST_Seq.from_array(e).optimize(compressor);
}
return make_node(AST_Seq, self, {
car: e,
cdr: make_node(AST_True, self)
}).optimize(compressor);
}
var seq = self.lift_sequences(compressor); var seq = self.lift_sequences(compressor);
if (seq !== self) { if (seq !== self) {
return seq; return seq;
} }
var e = self.expression;
if (compressor.option("side_effects") && self.operator == "void") { if (compressor.option("side_effects") && self.operator == "void") {
e = e.drop_side_effect_free(compressor); e = e.drop_side_effect_free(compressor);
if (e) { if (e) {
@@ -3070,9 +3199,14 @@ merge(Compressor.prototype, {
if (e instanceof AST_Binary if (e instanceof AST_Binary
&& (self.operator == "+" || self.operator == "-") && (self.operator == "+" || self.operator == "-")
&& (e.operator == "*" || e.operator == "/" || e.operator == "%")) { && (e.operator == "*" || e.operator == "/" || e.operator == "%")) {
self.expression = e.left; return make_node(AST_Binary, self, {
e.left = self; operator: e.operator,
return e.optimize(compressor); left: make_node(AST_UnaryPrefix, e.left, {
operator: self.operator,
expression: e.left
}),
right: e.right
});
} }
// avoids infinite recursion of numerals // avoids infinite recursion of numerals
if (self.operator != "-" if (self.operator != "-"
@@ -3091,23 +3225,25 @@ merge(Compressor.prototype, {
if (this.left instanceof AST_Seq) { if (this.left instanceof AST_Seq) {
var seq = this.left; var seq = this.left;
var x = seq.to_array(); var x = seq.to_array();
this.left = x.pop(); var e = this.clone();
x.push(this); e.left = x.pop();
x.push(e);
return AST_Seq.from_array(x).optimize(compressor); return AST_Seq.from_array(x).optimize(compressor);
} }
if (this.right instanceof AST_Seq && !this.left.has_side_effects(compressor)) { if (this.right instanceof AST_Seq && !this.left.has_side_effects(compressor)) {
var assign = this.operator == "=" && this.left instanceof AST_SymbolRef; var assign = this.operator == "=" && this.left instanceof AST_SymbolRef;
var root = this.right; var root = this.right.clone();
var cursor, seq = root; var cursor, seq = root;
while (assign || !seq.car.has_side_effects(compressor)) { while (assign || !seq.car.has_side_effects(compressor)) {
cursor = seq; cursor = seq;
if (seq.cdr instanceof AST_Seq) { if (seq.cdr instanceof AST_Seq) {
seq = seq.cdr; seq = seq.cdr = seq.cdr.clone();
} else break; } else break;
} }
if (cursor) { if (cursor) {
this.right = cursor.cdr; var e = this.clone();
cursor.cdr = this; e.right = cursor.cdr;
cursor.cdr = e;
return root.optimize(compressor); return root.optimize(compressor);
} }
} }
@@ -3485,12 +3621,11 @@ merge(Compressor.prototype, {
OPT(AST_SymbolRef, function(self, compressor){ OPT(AST_SymbolRef, function(self, compressor){
var def = self.resolve_defines(compressor); var def = self.resolve_defines(compressor);
if (def) { if (def) {
return def; return def.optimize(compressor);
} }
// testing against !self.scope.uses_with first is an optimization // testing against !self.scope.uses_with first is an optimization
if (compressor.option("screw_ie8") if (compressor.option("screw_ie8")
&& self.undeclared() && self.undeclared()
&& !isLHS(self, compressor.parent())
&& (!self.scope.uses_with || !compressor.find_parent(AST_With))) { && (!self.scope.uses_with || !compressor.find_parent(AST_With))) {
switch (self.name) { switch (self.name) {
case "undefined": case "undefined":
@@ -3501,31 +3636,61 @@ merge(Compressor.prototype, {
return make_node(AST_Infinity, self).optimize(compressor); return make_node(AST_Infinity, self).optimize(compressor);
} }
} }
if (compressor.option("evaluate") && compressor.option("reduce_vars")) { if (compressor.option("evaluate")
&& compressor.option("reduce_vars")
&& is_lhs(self, compressor.parent()) !== self) {
var d = self.definition(); var d = self.definition();
if (d.fixed) { var fixed = self.fixed_value();
if (fixed) {
if (d.should_replace === undefined) { if (d.should_replace === undefined) {
var init = d.fixed.evaluate(compressor); var init = fixed.evaluate(compressor);
if (init !== d.fixed) { if (init !== fixed) {
init = make_node_from_constant(init, d.fixed).optimize(compressor); init = make_node_from_constant(init, fixed);
init = best_of_expression(init, d.fixed); var value = init.optimize(compressor).print_to_string().length;
var value = init.print_to_string().length; var fn;
if (has_symbol_ref(fixed)) {
fn = function() {
var result = init.optimize(compressor);
return result === init ? result.clone(true) : result;
};
} else {
value = Math.min(value, fixed.print_to_string().length);
fn = function() {
var result = best_of_expression(init.optimize(compressor), fixed);
return result === init || result === fixed ? result.clone(true) : result;
};
}
var name = d.name.length; var name = d.name.length;
var freq = d.references.length; var overhead = 0;
var overhead = d.global || !freq ? 0 : (name + 2 + value) / freq; if (compressor.option("unused") && (!d.global || compressor.option("toplevel"))) {
d.should_replace = value <= name + overhead ? init : false; overhead = (name + 2 + value) / d.references.length;
}
d.should_replace = value <= name + overhead ? fn : false;
} else { } else {
d.should_replace = false; d.should_replace = false;
} }
} }
if (d.should_replace) { if (d.should_replace) {
return d.should_replace.clone(true); return d.should_replace();
} }
} }
} }
return self; return self;
function has_symbol_ref(value) {
var found;
value.walk(new TreeWalker(function(node) {
if (node instanceof AST_SymbolRef) found = true;
if (found) return true;
}));
return found;
}
}); });
function is_atomic(lhs, self) {
return lhs instanceof AST_SymbolRef || lhs.TYPE === self.TYPE;
}
OPT(AST_Undefined, function(self, compressor){ OPT(AST_Undefined, function(self, compressor){
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
var undef = find_variable(compressor, "undefined"); var undef = find_variable(compressor, "undefined");
@@ -3539,6 +3704,8 @@ merge(Compressor.prototype, {
return ref; return ref;
} }
} }
var lhs = is_lhs(compressor.self(), compressor.parent());
if (lhs && is_atomic(lhs, self)) return self;
return make_node(AST_UnaryPrefix, self, { return make_node(AST_UnaryPrefix, self, {
operator: "void", operator: "void",
expression: make_node(AST_Number, self, { expression: make_node(AST_Number, self, {
@@ -3548,8 +3715,13 @@ merge(Compressor.prototype, {
}); });
OPT(AST_Infinity, function(self, compressor){ OPT(AST_Infinity, function(self, compressor){
var retain = compressor.option("keep_infinity") && !find_variable(compressor, "Infinity"); var lhs = is_lhs(compressor.self(), compressor.parent());
return retain ? self : make_node(AST_Binary, self, { if (lhs && is_atomic(lhs, self)) return self;
if (compressor.option("keep_infinity")
&& !(lhs && !is_atomic(lhs, self))
&& !find_variable(compressor, "Infinity"))
return self;
return make_node(AST_Binary, self, {
operator: "/", operator: "/",
left: make_node(AST_Number, self, { left: make_node(AST_Number, self, {
value: 1 value: 1
@@ -3561,15 +3733,20 @@ merge(Compressor.prototype, {
}); });
OPT(AST_NaN, function(self, compressor){ OPT(AST_NaN, function(self, compressor){
return find_variable(compressor, "NaN") ? make_node(AST_Binary, self, { var lhs = is_lhs(compressor.self(), compressor.parent());
operator: "/", if (lhs && !is_atomic(lhs, self)
left: make_node(AST_Number, self, { || find_variable(compressor, "NaN")) {
value: 0 return make_node(AST_Binary, self, {
}), operator: "/",
right: make_node(AST_Number, self, { left: make_node(AST_Number, self, {
value: 0 value: 0
}) }),
}) : self; right: make_node(AST_Number, self, {
value: 0
})
});
}
return self;
}); });
var ASSIGN_OPS = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ]; var ASSIGN_OPS = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ];
@@ -3815,7 +3992,7 @@ merge(Compressor.prototype, {
OPT(AST_Dot, function(self, compressor){ OPT(AST_Dot, function(self, compressor){
var def = self.resolve_defines(compressor); var def = self.resolve_defines(compressor);
if (def) { if (def) {
return def; return def.optimize(compressor);
} }
var prop = self.property; var prop = self.property;
if (RESERVED_WORDS(prop) && !compressor.option("screw_ie8")) { if (RESERVED_WORDS(prop) && !compressor.option("screw_ie8")) {

View File

@@ -59,7 +59,7 @@ function OutputStream(options) {
comments : false, comments : false,
indent_level : 4, indent_level : 4,
indent_start : 0, indent_start : 0,
inline_script : false, inline_script : true,
keep_quoted_props: false, keep_quoted_props: false,
max_line_len : false, max_line_len : false,
preamble : null, preamble : null,
@@ -190,11 +190,7 @@ function OutputStream(options) {
var might_need_space = false; var might_need_space = false;
var might_need_semicolon = false; var might_need_semicolon = false;
var might_add_newline = 0; var might_add_newline = 0;
var last = null; var last = "";
function last_char() {
return last.charAt(last.length - 1);
};
var ensure_line_len = options.max_line_len ? function() { var ensure_line_len = options.max_line_len ? function() {
if (current_col > options.max_line_len) { if (current_col > options.max_line_len) {
@@ -218,10 +214,11 @@ function OutputStream(options) {
function print(str) { function print(str) {
str = String(str); str = String(str);
var ch = str.charAt(0); var ch = str.charAt(0);
var prev = last.charAt(last.length - 1);
if (might_need_semicolon) { if (might_need_semicolon) {
might_need_semicolon = false; might_need_semicolon = false;
if ((!ch || ";}".indexOf(ch) < 0) && !/[;]$/.test(last)) { if (prev == ":" && ch == "}" || (!ch || ";}".indexOf(ch) < 0) && prev != ";") {
if (options.semicolons || requireSemicolonChars(ch)) { if (options.semicolons || requireSemicolonChars(ch)) {
OUTPUT += ";"; OUTPUT += ";";
current_col++; current_col++;
@@ -258,7 +255,6 @@ function OutputStream(options) {
} }
if (might_need_space) { if (might_need_space) {
var prev = last_char();
if ((is_identifier_char(prev) if ((is_identifier_char(prev)
&& (is_identifier_char(ch) || ch == "\\")) && (is_identifier_char(ch) || ch == "\\"))
|| (ch == "/" && ch == prev) || (ch == "/" && ch == prev)

View File

@@ -44,7 +44,15 @@
"use strict"; "use strict";
function find_builtins() { function find_builtins() {
var a = []; // NaN will be included due to Number.NaN
var a = [
"null",
"true",
"false",
"Infinity",
"-Infinity",
"undefined",
];
[ Object, Array, Function, Number, [ Object, Array, Function, Number,
String, Boolean, Error, Math, String, Boolean, Error, Math,
Date, RegExp Date, RegExp
@@ -149,13 +157,12 @@ function mangle_properties(ast, options) {
// only function declarations after this line // only function declarations after this line
function can_mangle(name) { function can_mangle(name) {
if (!is_identifier(name)) return false;
if (unmangleable.indexOf(name) >= 0) return false; if (unmangleable.indexOf(name) >= 0) return false;
if (reserved.indexOf(name) >= 0) return false; if (reserved.indexOf(name) >= 0) return false;
if (options.only_cache) { if (options.only_cache) {
return cache.props.has(name); return cache.props.has(name);
} }
if (/^[0-9.]+$/.test(name)) return false; if (/^-?[0-9]+(\.[0-9]+)?(e[+-][0-9]+)?$/.test(name)) return false;
return true; 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": "2.8.21", "version": "2.8.24",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },

View File

@@ -11,8 +11,8 @@ if (!args.length) {
} }
args.push("--stats"); args.push("--stats");
var urls = [ var urls = [
"https://code.jquery.com/jquery-3.1.1.js", "https://code.jquery.com/jquery-3.2.1.js",
"https://code.angularjs.org/1.6.1/angular.js", "https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.4/angular.js",
"https://cdnjs.cloudflare.com/ajax/libs/mathjs/3.9.0/math.js", "https://cdnjs.cloudflare.com/ajax/libs/mathjs/3.9.0/math.js",
"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.js", "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.js",
"https://unpkg.com/react@15.3.2/dist/react.js", "https://unpkg.com/react@15.3.2/dist/react.js",

View File

@@ -1573,6 +1573,7 @@ var_side_effects_3: {
options = { options = {
collapse_vars: true, collapse_vars: true,
pure_getters: true, pure_getters: true,
unsafe: true,
} }
input: { input: {
var print = console.log.bind(console); var print = console.log.bind(console);
@@ -1591,3 +1592,49 @@ var_side_effects_3: {
} }
expect_stdout: true expect_stdout: true
} }
reassign_const_1: {
options = {
collapse_vars: true,
}
input: {
function f() {
const a = 1;
a = 2;
return a;
}
console.log(f());
}
expect: {
function f() {
const a = 1;
a = 2;
return a;
}
console.log(f());
}
expect_stdout: true
}
reassign_const_2: {
options = {
collapse_vars: true,
}
input: {
function f() {
const a = 1;
++a;
return a;
}
console.log(f());
}
expect: {
function f() {
const a = 1;
++a;
return a;
}
console.log(f());
}
expect_stdout: true
}

View File

@@ -962,3 +962,56 @@ condition_symbol_matches_consequent: {
} }
expect_stdout: "3 7 true 4" expect_stdout: "3 7 true 4"
} }
delete_conditional_1: {
options = {
booleans: true,
conditionals: true,
evaluate: true,
side_effects: true,
}
input: {
console.log(delete (1 ? undefined : x));
console.log(delete (1 ? void 0 : x));
console.log(delete (1 ? Infinity : x));
console.log(delete (1 ? 1 / 0 : x));
console.log(delete (1 ? NaN : x));
console.log(delete (1 ? 0 / 0 : x));
}
expect: {
console.log((void 0, !0));
console.log((void 0, !0));
console.log((1 / 0, !0));
console.log((1 / 0, !0));
console.log((NaN, !0));
console.log((NaN, !0));
}
expect_stdout: true
}
delete_conditional_2: {
options = {
booleans: true,
conditionals: true,
evaluate: true,
keep_infinity: true,
side_effects: true,
}
input: {
console.log(delete (0 ? x : undefined));
console.log(delete (0 ? x : void 0));
console.log(delete (0 ? x : Infinity));
console.log(delete (0 ? x : 1 / 0));
console.log(delete (0 ? x : NaN));
console.log(delete (0 ? x : 0 / 0));
}
expect: {
console.log((void 0, !0));
console.log((void 0, !0));
console.log((Infinity, !0));
console.log((1 / 0, !0));
console.log((NaN, !0));
console.log((NaN, !0));
}
expect_stdout: true
}

View File

@@ -974,3 +974,118 @@ issue_1715_4: {
} }
expect_stdout: "1" expect_stdout: "1"
} }
delete_assign_1: {
options = {
booleans: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a;
console.log(delete (a = undefined));
console.log(delete (a = void 0));
console.log(delete (a = Infinity));
console.log(delete (a = 1 / 0));
console.log(delete (a = NaN));
console.log(delete (a = 0 / 0));
}
expect: {
console.log((void 0, !0));
console.log((void 0, !0));
console.log((1 / 0, !0));
console.log((1 / 0, !0));
console.log((NaN, !0));
console.log((0 / 0, !0));
}
expect_stdout: true
}
delete_assign_2: {
options = {
booleans: true,
keep_infinity: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a;
console.log(delete (a = undefined));
console.log(delete (a = void 0));
console.log(delete (a = Infinity));
console.log(delete (a = 1 / 0));
console.log(delete (a = NaN));
console.log(delete (a = 0 / 0));
}
expect: {
console.log((void 0, !0));
console.log((void 0, !0));
console.log((Infinity, !0));
console.log((1 / 0, !0));
console.log((NaN, !0));
console.log((0 / 0, !0));
}
expect_stdout: true
}
issue_1830_1: {
options = {
unused: true,
}
input: {
!function() {
L: for (var b = console.log(1); !1;) continue L;
}();
}
expect: {
!function() {
L: for (console.log(1); !1;) continue L;
}();
}
expect_stdout: "1"
}
issue_1830_2: {
options = {
unused: true,
}
input: {
!function() {
L: for (var a = 1, b = console.log(a); --a;) continue L;
}();
}
expect: {
!function() {
var a = 1;
L: for (console.log(a); --a;) continue L;
}();
}
expect_stdout: "1"
}
reassign_const: {
options = {
cascade: true,
sequences: true,
side_effects: true,
unused: true,
}
input: {
function f() {
const a = 1;
a = 2;
return a;
}
console.log(f());
}
expect: {
function f() {
const a = 1;
return a = 2, a;
}
console.log(f());
}
expect_stdout: true
}

View File

@@ -857,3 +857,135 @@ issue_1760_2: {
} }
expect_stdout: "Infinity" expect_stdout: "Infinity"
} }
delete_expr_1: {
options = {
booleans: true,
evaluate: true,
}
input: {
console.log(delete undefined);
console.log(delete void 0);
console.log(delete Infinity);
console.log(delete (1 / 0));
console.log(delete NaN);
console.log(delete (0 / 0));
}
expect: {
console.log(delete undefined);
console.log((void 0, !0));
console.log(delete Infinity);
console.log((1 / 0, !0));
console.log(delete NaN);
console.log((0 / 0, !0));
}
expect_stdout: true
}
delete_expr_2: {
options = {
booleans: true,
evaluate: true,
keep_infinity: true,
}
input: {
console.log(delete undefined);
console.log(delete void 0);
console.log(delete Infinity);
console.log(delete (1 / 0));
console.log(delete NaN);
console.log(delete (0 / 0));
}
expect: {
console.log(delete undefined);
console.log((void 0, !0));
console.log(delete Infinity);
console.log((1 / 0, !0));
console.log(delete NaN);
console.log((0 / 0, !0));
}
expect_stdout: true
}
delete_binary_1: {
options = {
booleans: true,
evaluate: true,
side_effects: true,
}
input: {
console.log(delete (true && undefined));
console.log(delete (true && void 0));
console.log(delete (true && Infinity));
console.log(delete (true && (1 / 0)));
console.log(delete (true && NaN));
console.log(delete (true && (0 / 0)));
}
expect: {
console.log((void 0, !0));
console.log((void 0, !0));
console.log((1 / 0, !0));
console.log((1 / 0, !0));
console.log((NaN, !0));
console.log((NaN, !0));
}
expect_stdout: true
}
delete_binary_2: {
options = {
booleans: true,
evaluate: true,
keep_infinity: true,
side_effects: true,
}
input: {
console.log(delete (false || undefined));
console.log(delete (false || void 0));
console.log(delete (false || Infinity));
console.log(delete (false || (1 / 0)));
console.log(delete (false || NaN));
console.log(delete (false || (0 / 0)));
}
expect: {
console.log((void 0, !0));
console.log((void 0, !0));
console.log((Infinity, !0));
console.log((1 / 0, !0));
console.log((NaN, !0));
console.log((NaN, !0));
}
expect_stdout: true
}
Infinity_NaN_undefined_LHS: {
beautify = {
beautify: true,
}
input: {
function f() {
Infinity = Infinity;
++Infinity;
Infinity--;
NaN *= NaN;
++NaN;
NaN--;
undefined |= undefined;
++undefined;
undefined--;
}
}
expect_exact: [
"function f() {",
" Infinity = 1 / 0;",
" ++Infinity;",
" Infinity--;",
" NaN *= NaN;",
" ++NaN;",
" NaN--;",
" undefined |= void 0;",
" ++undefined;",
" undefined--;",
"}",
]
}

View File

@@ -93,3 +93,57 @@ issue_485_crashing_1530: {
this, void 0; this, void 0;
} }
} }
issue_1841_1: {
options = {
keep_fargs: false,
pure_getters: "strict",
reduce_vars: true,
unused: true,
}
input: {
var b = 10;
!function(arg) {
for (var key in "hi")
var n = arg.baz, n = [ b = 42 ];
}(--b);
console.log(b);
}
expect: {
var b = 10;
!function() {
for (var key in "hi") {
b = 42;
}
}(--b);
console.log(b);
}
expect_exact: "42"
}
issue_1841_2: {
options = {
keep_fargs: false,
pure_getters: false,
reduce_vars: true,
unused: true,
}
input: {
var b = 10;
!function(arg) {
for (var key in "hi")
var n = arg.baz, n = [ b = 42 ];
}(--b);
console.log(b);
}
expect: {
var b = 10;
!function(arg) {
for (var key in "hi") {
arg.baz, b = 42;
}
}(--b);
console.log(b);
}
expect_exact: "42"
}

View File

@@ -145,3 +145,18 @@ mixed: {
'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:129,8]', 'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:129,8]',
] ]
} }
issue_1801: {
options = {
booleans: true,
global_defs: {
"CONFIG.FOO.BAR": true,
},
}
input: {
console.log(CONFIG.FOO.BAR);
}
expect: {
console.log(!0);
}
}

View File

@@ -45,11 +45,10 @@ chained_evaluation_2: {
} }
expect: { expect: {
(function() { (function() {
var a = "long piece of string";
(function() { (function() {
var c; var c, b = "long piece of string";
c = f(a); c = f(b);
c.bar = a; c.bar = b;
})(); })();
})(); })();
} }

View File

@@ -35,11 +35,11 @@ f7: {
console.log(a, b); console.log(a, b);
} }
expect_exact: [ expect_exact: [
"var a = 100, b = 10;", "var b = 10;",
"", "",
"!function() {", "!function() {",
" for (;b = a, !1; ) ;", " for (;b = 100, !1; ) ;",
"}(), console.log(a, b);", "}(), console.log(100, b);",
] ]
expect_stdout: true expect_stdout: true
} }

239
test/compress/issue-1770.js Normal file
View File

@@ -0,0 +1,239 @@
mangle_props: {
mangle_props = {}
input: {
var obj = {
undefined: 1,
NaN: 2,
Infinity: 3,
"-Infinity": 4,
null: 5,
};
console.log(
obj[void 0],
obj[undefined],
obj["undefined"],
obj[0/0],
obj[NaN],
obj["NaN"],
obj[1/0],
obj[Infinity],
obj["Infinity"],
obj[-1/0],
obj[-Infinity],
obj["-Infinity"],
obj[null],
obj["null"]
);
}
expect: {
var obj = {
undefined: 1,
NaN: 2,
Infinity: 3,
"-Infinity": 4,
null: 5,
};
console.log(
obj[void 0],
obj[void 0],
obj["undefined"],
obj[0/0],
obj[NaN],
obj["NaN"],
obj[1/0],
obj[1/0],
obj["Infinity"],
obj[-1/0],
obj[-1/0],
obj["-Infinity"],
obj[null],
obj["null"]
);
}
expect_stdout: "1 1 1 2 2 2 3 3 3 4 4 4 5 5"
}
numeric_literal: {
beautify = {
beautify: true,
}
mangle_props = {}
input: {
var obj = {
0: 0,
"-0": 1,
42: 2,
"42": 3,
0x25: 4,
"0x25": 5,
1E42: 6,
"1E42": 7,
"1e+42": 8,
};
console.log(obj[-0], obj[-""], obj["-0"]);
console.log(obj[42], obj["42"]);
console.log(obj[0x25], obj["0x25"], obj[37], obj["37"]);
console.log(obj[1E42], obj["1E42"], obj["1e+42"]);
}
expect_exact: [
'var obj = {',
' 0: 0,',
' "-0": 1,',
' 42: 2,',
' "42": 3,',
' 37: 4,',
' a: 5,',
' 1e42: 6,',
' b: 7,',
' "1e+42": 8',
'};',
'',
'console.log(obj[-0], obj[-""], obj["-0"]);',
'',
'console.log(obj[42], obj["42"]);',
'',
'console.log(obj[37], obj["a"], obj[37], obj["37"]);',
'',
'console.log(obj[1e42], obj["b"], obj["1e+42"]);',
]
expect_stdout: [
"0 0 1",
"3 3",
"4 5 4 4",
"8 7 8",
]
}
identifier: {
mangle_props = {}
input: {
var obj = {
abstract: 1,
boolean: 2,
byte: 3,
char: 4,
class: 5,
double: 6,
enum: 7,
export: 8,
extends: 9,
final: 10,
float: 11,
goto: 12,
implements: 13,
import: 14,
int: 15,
interface: 16,
let: 17,
long: 18,
native: 19,
package: 20,
private: 21,
protected: 22,
public: 23,
short: 24,
static: 25,
super: 26,
synchronized: 27,
this: 28,
throws: 29,
transient: 30,
volatile: 31,
yield: 32,
false: 33,
null: 34,
true: 35,
break: 36,
case: 37,
catch: 38,
const: 39,
continue: 40,
debugger: 41,
default: 42,
delete: 43,
do: 44,
else: 45,
finally: 46,
for: 47,
function: 48,
if: 49,
in: 50,
instanceof: 51,
new: 52,
return: 53,
switch: 54,
throw: 55,
try: 56,
typeof: 57,
var: 58,
void: 59,
while: 60,
with: 61,
};
}
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,
A: 27,
B: 28,
C: 29,
D: 30,
F: 31,
G: 32,
false: 33,
null: 34,
true: 35,
H: 36,
I: 37,
J: 38,
K: 39,
L: 40,
M: 41,
N: 42,
O: 43,
P: 44,
Q: 45,
R: 46,
S: 47,
T: 48,
U: 49,
V: 50,
W: 51,
X: 52,
Y: 53,
Z: 54,
$: 55,
_: 56,
aa: 57,
ba: 58,
ca: 59,
da: 60,
ea: 61,
};
}
}

View File

@@ -0,0 +1,19 @@
unary_prefix: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
console.log(function() {
var x = -(2 / 3);
return x;
}());
}
expect: {
console.log(function() {
return -2 / 3;
}());
}
expect_stdout: true
}

134
test/compress/issue-1833.js Normal file
View File

@@ -0,0 +1,134 @@
iife_for: {
options = {
negate_iife: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f() {
function g() {
L: for (;;) break L;
}
g();
}
f();
}
expect: {
!function() {
!function() {
L: for (;;) break L;
}();
}();
}
}
iife_for_in: {
options = {
negate_iife: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f() {
function g() {
L: for (var a in x) break L;
}
g();
}
f();
}
expect: {
!function() {
!function() {
L: for (var a in x) break L;
}();
}();
}
}
iife_do: {
options = {
negate_iife: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f() {
function g() {
L: do {
break L;
} while (1);
}
g();
}
f();
}
expect: {
!function() {
!function() {
L: do {
break L;
} while (1);
}();
}();
}
}
iife_while: {
options = {
negate_iife: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f() {
function g() {
L: while (1) break L;
}
g();
}
f();
}
expect: {
!function() {
!function() {
L: while (1) break L;
}();
}();
}
}
label_do: {
options = {
evaluate: true,
loops: true,
}
input: {
L: do {
continue L;
} while (0);
}
expect: {
L: do {
continue L;
} while (0);
}
}
label_while: {
options = {
evaluate: true,
dead_code: true,
loops: true,
}
input: {
function f() {
L: while (0) continue L;
}
}
expect_exact: "function f(){L:;}"
}

View File

@@ -215,8 +215,7 @@ evaluate: {
a(); a();
for(;;) for(;;)
c(); c();
// rule disabled due to issue_1532 d();
do d(); while (false);
} }
} }
@@ -458,3 +457,26 @@ issue_1648: {
} }
expect_exact: "function f(){for(x();1;);}" expect_exact: "function f(){for(x();1;);}"
} }
do_switch: {
options = {
evaluate: true,
loops: true,
}
input: {
do {
switch (a) {
case b:
continue;
}
} while (false);
}
expect: {
do {
switch (a) {
case b:
continue;
}
} while (false);
}
}

View File

@@ -0,0 +1,121 @@
strict: {
options = {
pure_getters: "strict",
reduce_vars: false,
side_effects: true,
toplevel: true,
}
input: {
var a, b = null, c = {};
a.prop;
b.prop;
c.prop;
d.prop;
null.prop;
(void 0).prop;
undefined.prop;
}
expect: {
var a, b = null, c = {};
a.prop;
b.prop;
c.prop;
d.prop;
null.prop;
(void 0).prop;
(void 0).prop;
}
}
strict_reduce_vars: {
options = {
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
toplevel: true,
}
input: {
var a, b = null, c = {};
a.prop;
b.prop;
c.prop;
d.prop;
null.prop;
(void 0).prop;
undefined.prop;
}
expect: {
var a, b = null, c = {};
a.prop;
b.prop;
d.prop;
null.prop;
(void 0).prop;
(void 0).prop;
}
}
unsafe: {
options = {
pure_getters: true,
reduce_vars: false,
side_effects: true,
toplevel: true,
}
input: {
var a, b = null, c = {};
a.prop;
b.prop;
c.prop;
d.prop;
null.prop;
(void 0).prop;
undefined.prop;
}
expect: {
var a, b = null, c = {};
d;
null.prop;
(void 0).prop;
(void 0).prop;
}
}
unsafe_reduce_vars: {
options = {
pure_getters: true,
reduce_vars: true,
side_effects: true,
toplevel: true,
}
input: {
var a, b = null, c = {};
a.prop;
b.prop;
c.prop;
d.prop;
null.prop;
(void 0).prop;
undefined.prop;
}
expect: {
var a, b = null, c = {};
d;
null.prop;
(void 0).prop;
(void 0).prop;
}
}
chained: {
options = {
pure_getters: "strict",
side_effects: true,
}
input: {
a.b.c;
}
expect: {
a.b.c;
}
}

View File

@@ -41,22 +41,22 @@ reduce_vars: {
var A = 1; var A = 1;
(function() { (function() {
console.log(-3); console.log(-3);
console.log(-4); console.log(A - 5);
})(); })();
(function f1() { (function f1() {
var a = 2; var a = 2;
console.log(-3); console.log(a - 5);
eval("console.log(a);"); eval("console.log(a);");
})(); })();
(function f2(eval) { (function f2(eval) {
var a = 2; var a = 2;
console.log(-3); console.log(a - 5);
eval("console.log(a);"); eval("console.log(a);");
})(eval); })(eval);
(function() { (function() {
return "yes"; return "yes";
})(); })();
console.log(2); console.log(A + 1);
} }
expect_stdout: true expect_stdout: true
} }
@@ -300,7 +300,7 @@ unsafe_evaluate_array: {
} }
} }
unsafe_evaluate_equality: { unsafe_evaluate_equality_1: {
options = { options = {
evaluate : true, evaluate : true,
reduce_vars : true, reduce_vars : true,
@@ -308,47 +308,62 @@ unsafe_evaluate_equality: {
unused : true unused : true
} }
input: { input: {
function f0(){ function f0() {
var a = {}; var a = {};
console.log(a === a); return a === a;
} }
function f1() {
function f1(){
var a = []; var a = [];
console.log(a === a); return a === a;
} }
console.log(f0(), f1());
}
expect: {
function f0() {
return true;
}
function f1() {
return true;
}
console.log(f0(), f1());
}
expect_stdout: true
}
function f2(){ unsafe_evaluate_equality_2: {
options = {
collapse_vars: true,
evaluate : true,
passes : 2,
reduce_vars : true,
unsafe : true,
unused : true
}
input: {
function f2() {
var a = {a:1, b:2}; var a = {a:1, b:2};
var b = a; var b = a;
var c = a; var c = a;
console.log(b === c); return b === c;
} }
function f3() {
function f3(){
var a = [1, 2, 3]; var a = [1, 2, 3];
var b = a; var b = a;
var c = a; var c = a;
console.log(b === c); return b === c;
} }
console.log(f2(), f3());
} }
expect: { expect: {
function f0(){ function f2() {
console.log(true); return true;
} }
function f3() {
function f1(){ return true;
console.log(true);
}
function f2(){
console.log(true);
}
function f3(){
console.log(true);
} }
console.log(f2(), f3());
} }
expect_stdout: true
} }
passes: { passes: {
@@ -1717,7 +1732,10 @@ redefine_arguments_3: {
console.log(function() { console.log(function() {
var arguments; var arguments;
return typeof arguments; return typeof arguments;
}(), "number", "undefined"); }(), "number", function(x) {
var arguments = x;
return typeof arguments;
}());
} }
expect_stdout: "object number undefined" expect_stdout: "object number undefined"
} }
@@ -1866,3 +1884,288 @@ delay_def: {
} }
expect_stdout: true expect_stdout: true
} }
booleans: {
options = {
booleans: true,
evaluate: true,
reduce_vars: true,
}
input: {
console.log(function(a) {
if (a != 0);
switch (a) {
case 0:
return "FAIL";
case false:
return "PASS";
}
}(false));
}
expect: {
console.log(function(a) {
if (!1);
switch (!1) {
case 0:
return "FAIL";
case !1:
return "PASS";
}
}(!1));
}
expect_stdout: "PASS"
}
side_effects_assign: {
options = {
evaluate: true,
reduce_vars: true,
sequences: true,
side_effects: true,
toplevel: true,
}
input: {
var a = typeof void (a && a.in == 1, 0);
console.log(a);
}
expect: {
var a = typeof void (a && a.in);
console.log(a);
}
expect_stdout: "undefined"
}
pure_getters_1: {
options = {
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
toplevel: true,
}
input: {
try {
var a = (a.b, 2);
} catch (e) {}
console.log(a);
}
expect: {
try {
var a = (a.b, 2);
} catch (e) {}
console.log(a);
}
expect_stdout: "undefined"
}
pure_getters_2: {
options = {
pure_getters: "strict",
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a;
var a = a && a.b;
}
expect: {
var a;
var a = a && a.b;
}
}
pure_getters_3: {
options = {
pure_getters: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a;
var a = a && a.b;
}
expect: {
}
}
catch_var: {
options = {
booleans: true,
evaluate: true,
reduce_vars: true,
}
input: {
try {
throw {};
} catch (e) {
var e;
console.log(!!e);
}
}
expect: {
try {
throw {};
} catch (e) {
var e;
console.log(!!e);
}
}
expect_stdout: "true"
}
issue_1814_1: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
const a = 42;
!function() {
var b = a;
!function(a) {
console.log(a++, b);
}(0);
}();
}
expect: {
const a = 42;
!function() {
!function(a) {
console.log(a++, 42);
}(0);
}();
}
expect_stdout: "0 42"
}
issue_1814_2: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
const a = "32";
!function() {
var b = a + 1;
!function(a) {
console.log(a++, b);
}(0);
}();
}
expect: {
const a = "32";
!function() {
!function(a) {
console.log(a++, "321");
}(0);
}();
}
expect_stdout: "0 '321'"
}
try_abort: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
!function() {
try {
var a = 1;
throw "";
var b = 2;
} catch (e) {
}
console.log(a, b);
}();
}
expect: {
!function() {
try {
var a = 1;
throw "";
var b = 2;
} catch (e) {
}
console.log(a, b);
}();
}
expect_stdout: "1 undefined"
}
issue_1865: {
options = {
evaluate: true,
reduce_vars: true,
unsafe: true,
}
input: {
function f(some) {
some.thing = false;
}
console.log(function() {
var some = { thing: true };
f(some);
return some.thing;
}());
}
expect: {
function f(some) {
some.thing = false;
}
console.log(function() {
var some = { thing: true };
f(some);
return some.thing;
}());
}
expect_stdout: true
}
issue_1922_1: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
console.log(function(a) {
arguments[0] = 2;
return a;
}(1));
}
expect: {
console.log(function(a) {
arguments[0] = 2;
return a;
}(1));
}
expect_stdout: "2"
}
issue_1922_2: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
console.log(function() {
var a;
eval("a = 1");
return a;
}(1));
}
expect: {
console.log(function() {
var a;
eval("a = 1");
return a;
}(1));
}
expect_stdout: "1"
}

View File

@@ -466,3 +466,171 @@ issue_1758: {
} }
expect_stdout: "undefined" expect_stdout: "undefined"
} }
delete_seq_1: {
options = {
booleans: true,
side_effects: true,
}
input: {
console.log(delete (1, undefined));
console.log(delete (1, void 0));
console.log(delete (1, Infinity));
console.log(delete (1, 1 / 0));
console.log(delete (1, NaN));
console.log(delete (1, 0 / 0));
}
expect: {
console.log((void 0, !0));
console.log((void 0, !0));
console.log((1 / 0, !0));
console.log((1 / 0, !0));
console.log((NaN, !0));
console.log((0 / 0, !0));
}
expect_stdout: true
}
delete_seq_2: {
options = {
booleans: true,
side_effects: true,
}
input: {
console.log(delete (1, 2, undefined));
console.log(delete (1, 2, void 0));
console.log(delete (1, 2, Infinity));
console.log(delete (1, 2, 1 / 0));
console.log(delete (1, 2, NaN));
console.log(delete (1, 2, 0 / 0));
}
expect: {
console.log((void 0, !0));
console.log((void 0, !0));
console.log((1 / 0, !0));
console.log((1 / 0, !0));
console.log((NaN, !0));
console.log((0 / 0, !0));
}
expect_stdout: true
}
delete_seq_3: {
options = {
booleans: true,
keep_infinity: true,
side_effects: true,
}
input: {
console.log(delete (1, 2, undefined));
console.log(delete (1, 2, void 0));
console.log(delete (1, 2, Infinity));
console.log(delete (1, 2, 1 / 0));
console.log(delete (1, 2, NaN));
console.log(delete (1, 2, 0 / 0));
}
expect: {
console.log((void 0, !0));
console.log((void 0, !0));
console.log((Infinity, !0));
console.log((1 / 0, !0));
console.log((NaN, !0));
console.log((0 / 0, !0));
}
expect_stdout: true
}
delete_seq_4: {
options = {
booleans: true,
sequences: true,
side_effects: true,
}
input: {
function f() {}
console.log(delete (f(), undefined));
console.log(delete (f(), void 0));
console.log(delete (f(), Infinity));
console.log(delete (f(), 1 / 0));
console.log(delete (f(), NaN));
console.log(delete (f(), 0 / 0));
}
expect: {
function f() {}
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0));
}
expect_stdout: true
}
delete_seq_5: {
options = {
booleans: true,
keep_infinity: true,
sequences: true,
side_effects: true,
}
input: {
function f() {}
console.log(delete (f(), undefined));
console.log(delete (f(), void 0));
console.log(delete (f(), Infinity));
console.log(delete (f(), 1 / 0));
console.log(delete (f(), NaN));
console.log(delete (f(), 0 / 0));
}
expect: {
function f() {}
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0));
}
expect_stdout: true
}
delete_seq_6: {
options = {
booleans: true,
side_effects: true,
}
input: {
var a;
console.log(delete (1, a));
}
expect: {
var a;
console.log((a, !0));
}
expect_stdout: true
}
reassign_const: {
options = {
cascade: true,
sequences: true,
side_effects: true,
}
input: {
function f() {
const a = 1;
a++;
return a;
}
console.log(f());
}
expect: {
function f() {
const a = 1;
return a++, a;
}
console.log(f());
}
expect_stdout: true
}

View File

@@ -1,18 +1,37 @@
var vm = require("vm"); var vm = require("vm");
function safe_log(arg) {
if (arg) switch (typeof arg) {
case "function":
return arg.toString();
case "object":
if (/Error$/.test(arg.name)) return arg.toString();
arg.constructor.toString();
for (var key in arg) {
arg[key] = safe_log(arg[key]);
}
}
return arg;
}
var FUNC_TOSTRING = [ var FUNC_TOSTRING = [
"Function.prototype.toString = Function.prototype.valueOf = function() {", "Function.prototype.toString = Function.prototype.valueOf = function() {",
" var ids = [];", " var id = 0;",
" return function() {", " return function() {",
" var i = ids.indexOf(this);", ' if (this === Array) return "[Function: Array]";',
" if (i < 0) {", ' if (this === Object) return "[Function: Object]";',
" i = ids.length;", " var i = this.name;",
" ids.push(this);", ' if (typeof i != "number") {',
" i = ++id;",
' Object.defineProperty(this, "name", {',
" get: function() {",
" return i;",
" }",
" });",
" }", " }",
' return "[Function: __func_" + i + "__]";', ' return "[Function: " + i + "]";',
" }", " }",
"}();", "}();",
""
].join("\n"); ].join("\n");
exports.run_code = function(code) { exports.run_code = function(code) {
var stdout = ""; var stdout = "";
@@ -21,15 +40,18 @@ exports.run_code = function(code) {
stdout += chunk; stdout += chunk;
}; };
try { try {
new vm.Script(FUNC_TOSTRING + code).runInNewContext({ vm.runInNewContext([
FUNC_TOSTRING,
"!function() {",
code,
"}();",
].join("\n"), {
console: { console: {
log: function() { log: function() {
return console.log.apply(console, [].map.call(arguments, function(arg) { return console.log.apply(console, [].map.call(arguments, safe_log));
return typeof arg == "function" ? arg.toString() : arg;
}));
} }
} }
}, { timeout: 30000 }); }, { timeout: 5000 });
return stdout; return stdout;
} catch (ex) { } catch (ex) {
return ex; return ex;

View File

@@ -2,7 +2,7 @@
// derived from https://github.com/qfox/uglyfuzzer by Peter van der Zee // derived from https://github.com/qfox/uglyfuzzer by Peter van der Zee
"use strict"; "use strict";
// check both cli and file modes of nodejs (!). See #1695 for details. and the various settings of uglify. // check both CLI and file modes of nodejs (!). See #1695 for details. and the various settings of uglify.
// bin/uglifyjs s.js -c && bin/uglifyjs s.js -c passes=3 && bin/uglifyjs s.js -c passes=3 -m // bin/uglifyjs s.js -c && bin/uglifyjs s.js -c passes=3 && bin/uglifyjs s.js -c passes=3 -m
// cat s.js | node && node s.js && bin/uglifyjs s.js -c | node && bin/uglifyjs s.js -c passes=3 | node && bin/uglifyjs s.js -c passes=3 -m | node // cat s.js | node && node s.js && bin/uglifyjs s.js -c | node && bin/uglifyjs s.js -c passes=3 | node && bin/uglifyjs s.js -c passes=3 -m | node
@@ -20,49 +20,26 @@ var MAX_GENERATED_TOPLEVELS_PER_RUN = 1;
var MAX_GENERATION_RECURSION_DEPTH = 12; var MAX_GENERATION_RECURSION_DEPTH = 12;
var INTERVAL_COUNT = 100; var INTERVAL_COUNT = 100;
var STMT_BLOCK = 0; var STMT_ARG_TO_ID = Object.create(null);
var STMT_IF_ELSE = 1; var STMTS_TO_USE = [];
var STMT_DO_WHILE = 2; function STMT_(name) {
var STMT_WHILE = 3; return STMT_ARG_TO_ID[name] = STMTS_TO_USE.push(STMTS_TO_USE.length) - 1;
var STMT_FOR_LOOP = 4; }
var STMT_SEMI = 5;
var STMT_EXPR = 6; var STMT_BLOCK = STMT_("block");
var STMT_SWITCH = 7; var STMT_IF_ELSE = STMT_("ifelse");
var STMT_VAR = 8; var STMT_DO_WHILE = STMT_("dowhile");
var STMT_RETURN_ETC = 9; var STMT_WHILE = STMT_("while");
var STMT_FUNC_EXPR = 10; var STMT_FOR_LOOP = STMT_("forloop");
var STMT_TRY = 11; var STMT_FOR_IN = STMT_("forin");
var STMT_C = 12; var STMT_SEMI = STMT_("semi");
var STMTS_TO_USE = [ var STMT_EXPR = STMT_("expr");
STMT_BLOCK, var STMT_SWITCH = STMT_("switch");
STMT_IF_ELSE, var STMT_VAR = STMT_("var");
STMT_DO_WHILE, var STMT_RETURN_ETC = STMT_("stop");
STMT_WHILE, var STMT_FUNC_EXPR = STMT_("funcexpr");
STMT_FOR_LOOP, var STMT_TRY = STMT_("try");
STMT_SEMI, var STMT_C = STMT_("c");
STMT_EXPR,
STMT_SWITCH,
STMT_VAR,
STMT_RETURN_ETC,
STMT_FUNC_EXPR,
STMT_TRY,
STMT_C,
];
var STMT_ARG_TO_ID = {
block: STMT_BLOCK,
ifelse: STMT_IF_ELSE,
dowhile: STMT_DO_WHILE,
while: STMT_WHILE,
forloop: STMT_FOR_LOOP,
semi: STMT_SEMI,
expr: STMT_EXPR,
switch: STMT_SWITCH,
var: STMT_VAR,
stop: STMT_RETURN_ETC,
funcexpr: STMT_FUNC_EXPR,
try: STMT_TRY,
c: STMT_C,
};
var STMT_FIRST_LEVEL_OVERRIDE = -1; var STMT_FIRST_LEVEL_OVERRIDE = -1;
var STMT_SECOND_LEVEL_OVERRIDE = -1; var STMT_SECOND_LEVEL_OVERRIDE = -1;
@@ -71,6 +48,8 @@ var STMT_COUNT_FROM_GLOBAL = true; // count statement depth from nearest functio
var num_iterations = +process.argv[2] || 1/0; var num_iterations = +process.argv[2] || 1/0;
var verbose = false; // log every generated test var verbose = false; // log every generated test
var verbose_interval = false; // log every 100 generated tests var verbose_interval = false; // log every 100 generated tests
var verbose_error = false;
var use_strict = false;
for (var i = 2; i < process.argv.length; ++i) { for (var i = 2; i < process.argv.length; ++i) {
switch (process.argv[i]) { switch (process.argv[i]) {
case '-v': case '-v':
@@ -79,6 +58,9 @@ for (var i = 2; i < process.argv.length; ++i) {
case '-V': case '-V':
verbose_interval = true; verbose_interval = true;
break; break;
case '-E':
verbose_error = true;
break;
case '-t': case '-t':
MAX_GENERATED_TOPLEVELS_PER_RUN = +process.argv[++i]; MAX_GENERATED_TOPLEVELS_PER_RUN = +process.argv[++i];
if (!MAX_GENERATED_TOPLEVELS_PER_RUN) throw new Error('Must generate at least one toplevel per run'); if (!MAX_GENERATED_TOPLEVELS_PER_RUN) throw new Error('Must generate at least one toplevel per run');
@@ -97,6 +79,9 @@ for (var i = 2; i < process.argv.length; ++i) {
STMT_SECOND_LEVEL_OVERRIDE = STMT_ARG_TO_ID[name]; STMT_SECOND_LEVEL_OVERRIDE = STMT_ARG_TO_ID[name];
if (!(STMT_SECOND_LEVEL_OVERRIDE >= 0)) throw new Error('Unknown statement name; use -? to get a list'); if (!(STMT_SECOND_LEVEL_OVERRIDE >= 0)) throw new Error('Unknown statement name; use -? to get a list');
break; break;
case '--use-strict':
use_strict = true;
break;
case '--stmt-depth-from-func': case '--stmt-depth-from-func':
STMT_COUNT_FROM_GLOBAL = false; STMT_COUNT_FROM_GLOBAL = false;
break; break;
@@ -118,10 +103,12 @@ for (var i = 2; i < process.argv.length; ++i) {
console.log('<number>: generate this many cases (if used must be first arg)'); console.log('<number>: generate this many cases (if used must be first arg)');
console.log('-v: print every generated test case'); console.log('-v: print every generated test case');
console.log('-V: print every 100th generated test case'); console.log('-V: print every 100th generated test case');
console.log('-E: print generated test case with runtime error');
console.log('-t <int>: generate this many toplevels per run (more take longer)'); console.log('-t <int>: generate this many toplevels per run (more take longer)');
console.log('-r <int>: maximum recursion depth for generator (higher takes longer)'); console.log('-r <int>: maximum recursion depth for generator (higher takes longer)');
console.log('-s1 <statement name>: force the first level statement to be this one (see list below)'); console.log('-s1 <statement name>: force the first level statement to be this one (see list below)');
console.log('-s2 <statement name>: force the second level statement to be this one (see list below)'); console.log('-s2 <statement name>: force the second level statement to be this one (see list below)');
console.log('--use-strict: generate "use strict"');
console.log('--stmt-depth-from-func: reset statement depth counter at each function, counts from global otherwise'); console.log('--stmt-depth-from-func: reset statement depth counter at each function, counts from global otherwise');
console.log('--only-stmt <statement names>: a comma delimited white list of statements that may be generated'); console.log('--only-stmt <statement names>: a comma delimited white list of statements that may be generated');
console.log('--without-stmt <statement names>: a comma delimited black list of statements never to generate'); console.log('--without-stmt <statement names>: a comma delimited black list of statements never to generate');
@@ -135,6 +122,7 @@ for (var i = 2; i < process.argv.length; ++i) {
} }
var VALUES = [ var VALUES = [
'""',
'true', 'true',
'false', 'false',
' /[a2][^e]+$/ ', ' /[a2][^e]+$/ ',
@@ -221,15 +209,19 @@ var ASSIGNMENTS = [
'>>>=', '>>>=',
'%=' ]; '%=' ];
var UNARY_OPS = [ var UNARY_SAFE = [
'--', '+',
'++', '-',
'~', '~',
'!', '!',
'void ', 'void ',
'delete ', // should be safe, even `delete foo` and `delete f()` shouldn't crash 'delete ',
' - ', ];
' + ' ]; var UNARY_POSTFIX = [
'++',
'--',
];
var UNARY_PREFIX = UNARY_POSTFIX.concat(UNARY_SAFE);
var NO_COMMA = true; var NO_COMMA = true;
var COMMA_OK = false; var COMMA_OK = false;
@@ -250,26 +242,26 @@ var NO_DECL = true;
var DONT_STORE = true; var DONT_STORE = true;
var VAR_NAMES = [ var VAR_NAMES = [
'foo', 'a',
'bar', 'a',
'a',
'a', 'a',
'b', 'b',
'b',
'b',
'b',
'c', // prevent redeclaring this, avoid assigning to this 'c', // prevent redeclaring this, avoid assigning to this
'undefined', // fun! 'foo',
'eval', // mmmm, ok, also fun! 'foo',
'NaN', // mmmm, ok, also fun! 'bar',
'Infinity', // the fun never ends! 'bar',
'arguments', // this one is just creepy 'undefined',
'Math', // since Math is assumed to be a non-constructor/function it may trip certain cases 'NaN',
'Infinity',
'arguments',
'Math',
'parseInt', 'parseInt',
'parseFloat', ];
'isNaN',
'isFinite',
'decodeURI',
'decodeURIComponent',
'encodeURI',
'encodeURIComponent',
'Object'];
var INITIAL_NAMES_LEN = VAR_NAMES.length; var INITIAL_NAMES_LEN = VAR_NAMES.length;
var TYPEOF_OUTCOMES = [ var TYPEOF_OUTCOMES = [
@@ -286,15 +278,26 @@ var TYPEOF_OUTCOMES = [
var loops = 0; var loops = 0;
var funcs = 0; var funcs = 0;
var labels = 10000;
function rng(max) { function rng(max) {
var r = randomBytes(2).readUInt16LE(0) / 65536; var r = randomBytes(2).readUInt16LE(0) / 65536;
return Math.floor(max * r); return Math.floor(max * r);
} }
function strictMode() {
return use_strict && rng(4) == 0 ? '"use strict";' : '';
}
function createTopLevelCode() { function createTopLevelCode() {
if (rng(2) === 0) return createStatements(3, MAX_GENERATION_RECURSION_DEPTH, CANNOT_THROW, CANNOT_BREAK, CANNOT_CONTINUE, CANNOT_RETURN, 0); return [
return createFunctions(rng(MAX_GENERATED_TOPLEVELS_PER_RUN) + 1, MAX_GENERATION_RECURSION_DEPTH, IN_GLOBAL, ANY_TYPE, CANNOT_THROW, 0); strictMode(),
'var a = 100, b = 10, c = 0;',
rng(2) == 0
? createStatements(3, MAX_GENERATION_RECURSION_DEPTH, CANNOT_THROW, CANNOT_BREAK, CANNOT_CONTINUE, CANNOT_RETURN, 0)
: createFunctions(rng(MAX_GENERATED_TOPLEVELS_PER_RUN) + 1, MAX_GENERATION_RECURSION_DEPTH, IN_GLOBAL, ANY_TYPE, CANNOT_THROW, 0),
'console.log(null, a, b, c);' // preceding `null` makes for a cleaner output (empty string still shows up etc)
].join('\n');
} }
function createFunctions(n, recurmax, inGlobal, noDecl, canThrow, stmtDepth) { function createFunctions(n, recurmax, inGlobal, noDecl, canThrow, stmtDepth) {
@@ -306,6 +309,22 @@ function createFunctions(n, recurmax, inGlobal, noDecl, canThrow, stmtDepth) {
return s; return s;
} }
function createParams() {
var params = [];
for (var n = rng(4); --n >= 0;) {
params.push(createVarName(MANDATORY));
}
return params.join(', ');
}
function createArgs() {
var args = [];
for (var n = rng(4); --n >= 0;) {
args.push(createValue());
}
return args.join(', ');
}
function createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) { function createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) {
if (--recurmax < 0) { return ';'; } if (--recurmax < 0) { return ';'; }
if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0; if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0;
@@ -316,17 +335,29 @@ function createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) {
var s = ''; var s = '';
if (rng(5) === 0) { if (rng(5) === 0) {
// functions with functions. lower the recursion to prevent a mess. // functions with functions. lower the recursion to prevent a mess.
s = 'function ' + name + '(' + createVarName(MANDATORY) + '){' + createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), NOT_GLOBAL, ANY_TYPE, canThrow, stmtDepth) + '}\n'; s = [
'function ' + name + '(' + createParams() + '){',
strictMode(),
createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), NOT_GLOBAL, ANY_TYPE, canThrow, stmtDepth),
'}',
''
].join('\n');
} else { } else {
// functions with statements // functions with statements
s = 'function ' + name + '(' + createVarName(MANDATORY) + '){' + createStatements(3, recurmax, canThrow, CANNOT_THROW, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + '}\n'; s = [
'function ' + name + '(' + createParams() + '){',
strictMode(),
createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
'}',
''
].join('\n');
} }
VAR_NAMES.length = namesLenBefore; VAR_NAMES.length = namesLenBefore;
if (noDecl) s = '!' + s + '(' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')'; if (noDecl) s = 'var ' + createVarName(MANDATORY) + ' = ' + s + '(' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ');';
// avoid "function statements" (decl inside statements) // avoid "function statements" (decl inside statements)
else if (inGlobal || rng(10) > 0) s += name + '();' else if (inGlobal || rng(10) > 0) s += 'var ' + createVarName(MANDATORY) + ' = ' + name + '(' + createArgs() + ');';
return s; return s;
@@ -341,6 +372,40 @@ function createStatements(n, recurmax, canThrow, canBreak, canContinue, cannotRe
return s; return s;
} }
function enableLoopControl(flag, defaultValue) {
return Array.isArray(flag) && flag.indexOf("") < 0 ? flag.concat("") : flag || defaultValue;
}
function createLabel(canBreak, canContinue) {
var label;
if (rng(10) < 3) {
label = ++labels;
if (Array.isArray(canBreak)) {
canBreak = canBreak.slice();
} else {
canBreak = canBreak ? [ "" ] : [];
}
canBreak.push(label);
if (Array.isArray(canContinue)) {
canContinue = canContinue.slice();
} else {
canContinue = canContinue ? [ "" ] : [];
}
canContinue.push(label);
}
return {
break: canBreak,
continue: canContinue,
target: label ? "L" + label + ": " : ""
};
}
function getLabel(label) {
if (!Array.isArray(label)) return "";
label = label[rng(label.length)];
return label && " L" + label;
}
function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) { function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) {
++stmtDepth; ++stmtDepth;
var loop = ++loops; var loop = ++loops;
@@ -356,17 +421,36 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
switch (target) { switch (target) {
case STMT_BLOCK: case STMT_BLOCK:
return '{' + createStatements(rng(5) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + '}'; var label = createLabel(canBreak);
return label.target + '{' + createStatements(rng(5) + 1, recurmax, canThrow, label.break, canContinue, cannotReturn, stmtDepth) + '}';
case STMT_IF_ELSE: case STMT_IF_ELSE:
return 'if (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + (rng(2) === 1 ? ' else ' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) : ''); return 'if (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + (rng(2) === 1 ? ' else ' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) : '');
case STMT_DO_WHILE: case STMT_DO_WHILE:
return '{var brake' + loop + ' = 5; do {' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE, cannotReturn, stmtDepth) + '} while ((' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && --brake' + loop + ' > 0);}'; var label = createLabel(canBreak, canContinue);
canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
return '{var brake' + loop + ' = 5; ' + label.target + 'do {' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + '} while ((' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && --brake' + loop + ' > 0);}';
case STMT_WHILE: case STMT_WHILE:
return '{var brake' + loop + ' = 5; while ((' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && --brake' + loop + ' > 0)' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE, cannotReturn, stmtDepth) + '}'; var label = createLabel(canBreak, canContinue);
canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
return '{var brake' + loop + ' = 5; ' + label.target + 'while ((' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && --brake' + loop + ' > 0)' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + '}';
case STMT_FOR_LOOP: case STMT_FOR_LOOP:
return 'for (var brake' + loop + ' = 5; (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && brake' + loop + ' > 0; --brake' + loop + ')' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE, cannotReturn, stmtDepth); var label = createLabel(canBreak, canContinue);
canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
return label.target + 'for (var brake' + loop + ' = 5; (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && brake' + loop + ' > 0; --brake' + loop + ')' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth);
case STMT_FOR_IN:
var label = createLabel(canBreak, canContinue);
canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
var optElementVar = '';
if (rng(5) > 1) {
optElementVar = 'c = 1 + c; var ' + createVarName(MANDATORY) + ' = expr' + loop + '[key' + loop + ']; ';
}
return '{var expr' + loop + ' = ' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + '; ' + label.target + ' for (var key' + loop + ' in expr' + loop + ') {' + optElementVar + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + '}}';
case STMT_SEMI: case STMT_SEMI:
return ';'; return use_strict && rng(20) === 0 ? '"use strict";' : ';';
case STMT_EXPR: case STMT_EXPR:
return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ';'; return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ';';
case STMT_SWITCH: case STMT_SWITCH:
@@ -393,23 +477,27 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
return 'var ' + n1 + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ', ' + n2 + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';'; return 'var ' + n1 + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ', ' + n2 + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
} }
case STMT_RETURN_ETC: case STMT_RETURN_ETC:
switch (rng(3)) { switch (rng(8)) {
case 0:
case 1: case 1:
if (canBreak && rng(5) === 0) return 'break;';
if (canContinue && rng(5) === 0) return 'continue;';
if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
return '/*3*/return;';
case 2: case 2:
// must wrap in curlies to prevent orphaned `else` statement case 3:
if (canThrow && rng(5) === 0) return '{ throw ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}'; if (canBreak && rng(5) === 0) return 'break' + getLabel(canBreak) + ';';
if (canContinue && rng(5) === 0) return 'continue' + getLabel(canContinue) + ';';
if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';'; if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
return '{ /*1*/ return ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}'; if (rng(3) == 0) return '/*3*/return;';
default: return 'return ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
case 4:
// this is actually more like a parser test, but perhaps it hits some dead code elimination traps // this is actually more like a parser test, but perhaps it hits some dead code elimination traps
// must wrap in curlies to prevent orphaned `else` statement // must wrap in curlies to prevent orphaned `else` statement
// note: you can't `throw` without an expression so don't put a `throw` option in this case // note: you can't `throw` without an expression so don't put a `throw` option in this case
if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';'; if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
return '{ /*2*/ return\n' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}'; return '{ /*2*/ return\n' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}';
default:
// must wrap in curlies to prevent orphaned `else` statement
if (canThrow && rng(5) === 0) return '{ throw ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}';
if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
return '{ return ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}';
} }
case STMT_FUNC_EXPR: case STMT_FUNC_EXPR:
// "In non-strict mode code, functions can only be declared at top level, inside a block, or ..." // "In non-strict mode code, functions can only be declared at top level, inside a block, or ..."
@@ -440,90 +528,130 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
function createSwitchParts(recurmax, n, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) { function createSwitchParts(recurmax, n, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) {
var hadDefault = false; var hadDefault = false;
var s = ''; var s = [''];
canBreak = enableLoopControl(canBreak, CAN_BREAK);
while (n-- > 0) { while (n-- > 0) {
//hadDefault = n > 0; // disables weird `default` clause positioning (use when handling destabilizes) //hadDefault = n > 0; // disables weird `default` clause positioning (use when handling destabilizes)
if (hadDefault || rng(5) > 0) { if (hadDefault || rng(5) > 0) {
s += '' + s.push(
'case ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ':\n' + 'case ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ':',
createStatements(rng(3) + 1, recurmax, canThrow, CAN_BREAK, canContinue, cannotReturn, stmtDepth) + createStatements(rng(3) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
'\n' + rng(10) > 0 ? ' break;' : '/* fall-through */',
(rng(10) > 0 ? ' break;' : '/* fall-through */') + ''
'\n'; );
} else { } else {
hadDefault = true; hadDefault = true;
s += '' + s.push(
'default:\n' + 'default:',
createStatements(rng(3) + 1, recurmax, canThrow, CAN_BREAK, canContinue, cannotReturn, stmtDepth) + createStatements(rng(3) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
'\n'; ''
);
} }
} }
return s; return s.join('\n');
} }
function createExpression(recurmax, noComma, stmtDepth, canThrow) { function createExpression(recurmax, noComma, stmtDepth, canThrow) {
if (--recurmax < 0) { if (--recurmax < 0) {
return '(c = 1 + c, ' + createNestedBinaryExpr(recurmax, noComma) + ')'; // note: should return a simple non-recursing expression value! return '(c = 1 + c, ' + createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')'; // note: should return a simple non-recursing expression value!
} }
// since `a` and `b` are our canaries we want them more frequently than other expressions (1/3rd chance of a canary) // since `a` and `b` are our canaries we want them more frequently than other expressions (1/3rd chance of a canary)
var r = rng(6); switch (rng(6)) {
if (r < 1) return 'a++ + ' + _createExpression(recurmax, noComma, stmtDepth, canThrow); case 0:
if (r < 2) return '(--b) + ' + _createExpression(recurmax, noComma, stmtDepth, canThrow); return '(a++ + (' + _createExpression(recurmax, noComma, stmtDepth, canThrow) + '))';
if (r < 3) return '(c = c + 1) + ' + _createExpression(recurmax, noComma, stmtDepth, canThrow); // c only gets incremented case 1:
return '((--b) + (' + _createExpression(recurmax, noComma, stmtDepth, canThrow) + '))';
return _createExpression(recurmax, noComma, stmtDepth, canThrow); case 2:
return '((c = c + 1) + (' + _createExpression(recurmax, noComma, stmtDepth, canThrow) + '))'; // c only gets incremented
default:
return '(' + _createExpression(recurmax, noComma, stmtDepth, canThrow) + ')';
}
} }
function _createExpression(recurmax, noComma, stmtDepth, canThrow) { function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
switch (rng(15)) { var p = 0;
case 0: switch (rng(_createExpression.N)) {
return createUnaryOp() + (rng(2) === 1 ? 'a' : 'b'); case p++:
case 1: case p++:
return 'a' + (rng(2) == 1 ? '++' : '--'); return createUnaryPrefix() + (rng(2) === 1 ? 'a' : 'b');
case 2: case p++:
case p++:
return (rng(2) === 1 ? 'a' : 'b') + createUnaryPostfix();
case p++:
case p++:
// parens needed because assignments aren't valid unless they're the left-most op(s) in an expression // parens needed because assignments aren't valid unless they're the left-most op(s) in an expression
return '(b ' + createAssignment() + ' a)'; return 'b ' + createAssignment() + ' a';
case 3: case p++:
case p++:
return rng(2) + ' === 1 ? a : b'; return rng(2) + ' === 1 ? a : b';
case 4: case p++:
return createNestedBinaryExpr(recurmax, noComma) + createBinaryOp(noComma) + createExpression(recurmax, noComma, stmtDepth, canThrow); case p++:
case 5:
return createValue(); return createValue();
case 6: case p++:
return '(' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')'; return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow);
case 7: case p++:
return createExpression(recurmax, noComma, stmtDepth, canThrow) + '?' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ':' + createExpression(recurmax, noComma, stmtDepth, canThrow); return createExpression(recurmax, noComma, stmtDepth, canThrow) + '?' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ':' + createExpression(recurmax, noComma, stmtDepth, canThrow);
case 8: case p++:
case p++:
var nameLenBefore = VAR_NAMES.length; var nameLenBefore = VAR_NAMES.length;
var name = createVarName(MAYBE); // note: this name is only accessible from _within_ the function. and immutable at that. var name = createVarName(MAYBE); // note: this name is only accessible from _within_ the function. and immutable at that.
if (name === 'c') name = 'a'; if (name == 'c') name = 'a';
var s = ''; var s = [];
switch(rng(4)) { switch (rng(5)) {
case 0: case 0:
s = '(function ' + name + '(){' + createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + '})()'; s.push(
'(function ' + name + '(){',
strictMode(),
createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
'})()'
);
break; break;
case 1: case 1:
s = '+function ' + name + '(){' + createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + '}()'; s.push(
'+function ' + name + '(){',
strictMode(),
createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
'}()'
);
break; break;
case 2: case 2:
s = '!function ' + name + '(){' + createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + '}()'; s.push(
'!function ' + name + '(){',
strictMode(),
createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
'}()'
);
break;
case 3:
s.push(
'void function ' + name + '(){',
strictMode(),
createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
'}()'
);
break; break;
default: default:
s = 'void function ' + name + '(){' + createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + '}()'; var instantiate = rng(4) ? 'new ' : '';
s.push(
instantiate + 'function ' + name + '(){',
strictMode()
);
if (instantiate) for (var i = rng(4); --i >= 0;) {
if (rng(2)) s.push('this.' + getDotKey() + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ';');
else s.push('this[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']' + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ';');
}
s.push(
createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
'}'
);
break; break;
} }
VAR_NAMES.length = nameLenBefore; VAR_NAMES.length = nameLenBefore;
return s; return s.join('\n');
case 9: case p++:
case p++:
return createTypeofExpr(recurmax, stmtDepth, canThrow); return createTypeofExpr(recurmax, stmtDepth, canThrow);
case 10: case p++:
// you could statically infer that this is just `Math`, regardless of the other expression case p++:
// I don't think Uglify does this at this time...
return ''+
'new function(){ \n' +
(rng(2) === 1 ? createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + '\n' : '') +
'return Math;\n' +
'}';
case 11:
// more like a parser test but perhaps comment nodes mess up the analysis? // more like a parser test but perhaps comment nodes mess up the analysis?
// note: parens not needed for post-fix (since that's the default when ambiguous) // note: parens not needed for post-fix (since that's the default when ambiguous)
// for prefix ops we need parens to prevent accidental syntax errors. // for prefix ops we need parens to prevent accidental syntax errors.
@@ -533,58 +661,152 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
case 1: case 1:
return 'b/* ignore */--'; return 'b/* ignore */--';
case 2: case 2:
return '(++/* ignore */a)'; return '++/* ignore */a';
case 3: case 3:
return '(--/* ignore */b)'; return '--/* ignore */b';
case 4: case 4:
// only groups that wrap a single variable return a "Reference", so this is still valid. // only groups that wrap a single variable return a "Reference", so this is still valid.
// may just be a parser edge case that is invisible to uglify... // may just be a parser edge case that is invisible to uglify...
return '(--(b))'; return '--(b)';
case 5: case 5:
// classic 0.3-0.1 case; 1-0.1-0.1-0.1 is not 0.7 :) // classic 0.3-0.1 case; 1-0.1-0.1-0.1 is not 0.7 :)
return 'b + 1-0.1-0.1-0.1'; return 'b + 1-0.1-0.1-0.1';
default: default:
return '(--/* ignore */b)'; return '--/* ignore */b';
} }
case 12: case p++:
return createNestedBinaryExpr(recurmax, noComma); case p++:
case 13: return createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow);
case p++:
case p++:
return createUnarySafePrefix() + '(' + createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
case p++:
return " ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || a || 3).toString() "; return " ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || a || 3).toString() ";
case 14: case p++:
return " /[abc4]/.test(((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || b || 5).toString()) "; return " /[abc4]/.test(((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || b || 5).toString()) ";
case p++:
return " ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) +
") || " + rng(10) + ").toString()[" +
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "] ";
case p++:
return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow);
case p++:
return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow);
case p++:
return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '[' +
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']';
case p++:
return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '[' +
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']';
case p++:
return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '.' + getDotKey();
case p++:
return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '.' + getDotKey();
case p++:
var name = getVarName();
return name + ' && ' + name + '[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']';
case p++:
var name = getVarName();
return name + ' && ' + name + '.' + getDotKey();
} }
_createExpression.N = p;
return _createExpression(recurmax, noComma, stmtDepth, canThrow);
} }
function createNestedBinaryExpr(recurmax, noComma) { function createArrayLiteral(recurmax, noComma, stmtDepth, canThrow) {
recurmax = 3; // note that this generates 2^recurmax expression parts... make sure to cap it recurmax--;
return _createSimpleBinaryExpr(recurmax, noComma); var arr = "[";
for (var i = rng(6); --i >= 0;) {
// in rare cases produce an array hole element
var element = rng(20) ? createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) : "";
arr += element + ", ";
}
return arr + "]";
} }
function _createSimpleBinaryExpr(recurmax, noComma) {
var SAFE_KEYS = [
"length",
"foo",
"a",
"b",
"c",
"undefined",
"null",
"NaN",
"Infinity",
"in",
"var",
];
var KEYS = [
"''",
'"\t"',
'"-2"',
"0",
"1.5",
"3",
].concat(SAFE_KEYS);
function getDotKey() {
return SAFE_KEYS[rng(SAFE_KEYS.length)];
}
function createObjectLiteral(recurmax, noComma, stmtDepth, canThrow) {
recurmax--;
var obj = "({";
for (var i = rng(6); --i >= 0;) {
var key = KEYS[rng(KEYS.length)];
obj += key + ":(" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "), ";
}
return obj + "})";
}
function createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
recurmax = 3; // note that this generates 2^recurmax expression parts... make sure to cap it
return _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow);
}
function _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
return '(' + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow)
+ createBinaryOp(noComma) + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
}
function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
// intentionally generate more hardcore ops // intentionally generate more hardcore ops
if (--recurmax < 0) return createValue(); if (--recurmax < 0) return createValue();
var r = rng(30); var assignee, expr;
if (r === 0) return '(c = c + 1, ' + _createSimpleBinaryExpr(recurmax, noComma) + ')'; switch (rng(30)) {
var s = _createSimpleBinaryExpr(recurmax, noComma) + createBinaryOp(noComma) + _createSimpleBinaryExpr(recurmax, noComma); case 0:
if (r === 1) { return '(c = c + 1, ' + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
// try to get a generated name reachable from current scope. default to just `a` case 1:
var assignee = VAR_NAMES[INITIAL_NAMES_LEN + rng(VAR_NAMES.length - INITIAL_NAMES_LEN)] || 'a'; return '(' + createUnarySafePrefix() + '(' + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + '))';
return '( ' + assignee + createAssignment() + s + ')'; case 2:
assignee = getVarName();
return '(' + assignee + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
case 3:
assignee = getVarName();
expr = '(' + assignee + '[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow)
+ ']' + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
return canThrow && rng(10) == 0 ? expr : '(' + assignee + ' && ' + expr + ')';
case 4:
assignee = getVarName();
expr = '(' + assignee + '.' + getDotKey() + createAssignment()
+ _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
return canThrow && rng(10) == 0 ? expr : '(' + assignee + ' && ' + expr + ')';
default:
return _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow);
} }
return s;
} }
function createTypeofExpr(recurmax, stmtDepth, canThrow) { function createTypeofExpr(recurmax, stmtDepth, canThrow) {
switch (rng(8)) { switch (rng(8)) {
case 0: case 0:
return 'typeof ' + createVarName(MANDATORY, DONT_STORE) + ' === "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '"'; return '(typeof ' + createVarName(MANDATORY, DONT_STORE) + ' === "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
case 1: case 1:
return 'typeof ' + createVarName(MANDATORY, DONT_STORE) + ' !== "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '"'; return '(typeof ' + createVarName(MANDATORY, DONT_STORE) + ' !== "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
case 2: case 2:
return 'typeof ' + createVarName(MANDATORY, DONT_STORE) + ' == "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '"'; return '(typeof ' + createVarName(MANDATORY, DONT_STORE) + ' == "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
case 3: case 3:
return 'typeof ' + createVarName(MANDATORY, DONT_STORE) + ' != "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '"'; return '(typeof ' + createVarName(MANDATORY, DONT_STORE) + ' != "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
case 4: case 4:
return 'typeof ' + createVarName(MANDATORY, DONT_STORE); return '(typeof ' + createVarName(MANDATORY, DONT_STORE) + ')';
default: default:
return '(typeof ' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')'; return '(typeof ' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')';
} }
@@ -603,16 +825,31 @@ function createAssignment() {
return ASSIGNMENTS[rng(ASSIGNMENTS.length)]; return ASSIGNMENTS[rng(ASSIGNMENTS.length)];
} }
function createUnaryOp() { function createUnarySafePrefix() {
return UNARY_OPS[rng(UNARY_OPS.length)]; return UNARY_SAFE[rng(UNARY_SAFE.length)];
}
function createUnaryPrefix() {
return UNARY_PREFIX[rng(UNARY_PREFIX.length)];
}
function createUnaryPostfix() {
return UNARY_POSTFIX[rng(UNARY_POSTFIX.length)];
}
function getVarName() {
// try to get a generated name reachable from current scope. default to just `a`
return VAR_NAMES[INITIAL_NAMES_LEN + rng(VAR_NAMES.length - INITIAL_NAMES_LEN)] || 'a';
} }
function createVarName(maybe, dontStore) { function createVarName(maybe, dontStore) {
if (!maybe || rng(2) === 1) { if (!maybe || rng(2)) {
var r = rng(VAR_NAMES.length); var name = VAR_NAMES[rng(VAR_NAMES.length)];
var suffixed = rng(5) > 0; var suffix = rng(3);
var name = VAR_NAMES[r] + (suffixed ? '_' + (++loops) : ''); if (suffix) {
if (!dontStore && suffixed) VAR_NAMES.push(name); name += '_' + suffix;
if (!dontStore) VAR_NAMES.push(name);
}
return name; return name;
} }
return ''; return '';
@@ -655,8 +892,8 @@ var default_options = {
mangle: { mangle: {
"cache": null, "cache": null,
"eval": false, "eval": false,
"ie8": false,
"keep_fnames": false, "keep_fnames": false,
"screw_ie8": true,
"toplevel": false, "toplevel": false,
}, },
output: infer_options(UglifyJS.OutputStream), output: infer_options(UglifyJS.OutputStream),
@@ -711,17 +948,27 @@ function log(options) {
} else { } else {
console.log("// !!! uglify failed !!!"); console.log("// !!! uglify failed !!!");
console.log(uglify_code.stack); console.log(uglify_code.stack);
if (typeof original_result != "string") {
console.log();
console.log();
console.log("original stacktrace:");
console.log(original_result.stack);
}
} }
console.log("minify(options):"); console.log("minify(options):");
options = JSON.parse(options); options = JSON.parse(options);
console.log(options); console.log(options);
console.log(); console.log();
if (!ok) { if (!ok && typeof uglify_code == "string") {
Object.keys(default_options).forEach(log_suspects.bind(null, options)); Object.keys(default_options).forEach(log_suspects.bind(null, options));
console.log("!!!!!! Failed... round", round); console.log("!!!!!! Failed... round", round);
} }
} }
var fallback_options = [ JSON.stringify({
compress: false,
mangle: false
}) ];
var minify_options = require("./ufuzz.json").map(function(options) { var minify_options = require("./ufuzz.json").map(function(options) {
options.fromString = true; options.fromString = true;
return JSON.stringify(options); return JSON.stringify(options);
@@ -735,13 +982,9 @@ for (var round = 1; round <= num_iterations; round++) {
loops = 0; loops = 0;
funcs = 0; funcs = 0;
original_code = [ original_code = createTopLevelCode();
"var a = 100, b = 10, c = 0;", original_result = sandbox.run_code(original_code);
createTopLevelCode(), (typeof original_result != "string" ? fallback_options : minify_options).forEach(function(options) {
"console.log(null, a, b, c);" // preceding `null` makes for a cleaner output (empty string still shows up etc)
].join("\n");
minify_options.forEach(function(options) {
try { try {
uglify_code = UglifyJS.minify(original_code, JSON.parse(options)).code; uglify_code = UglifyJS.minify(original_code, JSON.parse(options)).code;
} catch (e) { } catch (e) {
@@ -750,11 +993,26 @@ for (var round = 1; round <= num_iterations; round++) {
ok = typeof uglify_code == "string"; ok = typeof uglify_code == "string";
if (ok) { if (ok) {
original_result = sandbox.run_code(original_code);
uglify_result = sandbox.run_code(uglify_code); uglify_result = sandbox.run_code(uglify_code);
ok = sandbox.same_stdout(original_result, uglify_result); ok = sandbox.same_stdout(original_result, uglify_result);
} else if (typeof original_result != "string") {
ok = uglify_code.name == original_result.name;
} }
if (verbose || (verbose_interval && !(round % INTERVAL_COUNT)) || !ok) log(options); if (verbose || (verbose_interval && !(round % INTERVAL_COUNT)) || !ok) log(options);
if (!ok && isFinite(num_iterations)) process.exit(1); else if (verbose_error && typeof original_result != "string") {
console.log("//=============================================================");
console.log("// original code");
try_beautify(original_code, original_result);
console.log();
console.log();
console.log("original result:");
console.log(original_result);
console.log();
}
if (!ok && isFinite(num_iterations)) {
console.log();
process.exit(1);
}
}); });
} }
console.log();

View File

@@ -1,19 +1,4 @@
[ [
{
"compress": {
"warnings": false
}
},
{
"compress": {
"warnings": false
},
"mangle": false
},
{
"compress": false,
"mangle": true
},
{ {
"compress": false, "compress": false,
"mangle": false, "mangle": false,
@@ -22,11 +7,33 @@
"bracketize": true "bracketize": true
} }
}, },
{
"compress": false
},
{
"compress": {
"warnings": false
},
"mangle": false
},
{
"compress": {
"warnings": false
}
},
{
"compress": {
"toplevel": true,
"warnings": false
},
"mangle": {
"toplevel": true
}
},
{ {
"compress": { "compress": {
"keep_fargs": false, "keep_fargs": false,
"passes": 3, "passes": 3,
"pure_getters": true,
"warnings": false "warnings": false
} }
} }