Compare commits

...

24 Commits

Author SHA1 Message Date
Alex Lam S.L
1ca43bcca1 v2.8.25 2017-05-15 19:45:33 +08:00
Alex Lam S.L
3ee1464aa4 Merge pull request #1938 from alexlamsl/v2.8.25 2017-05-15 19:44:44 +08:00
Alex Lam S.L
24967b8be8 fix & improve coverage of estree (#1935)
- fix `estree` conversion of getter/setter
- fix non-directive literal in `to_mozilla_ast()`
- revamp `test/mozilla-ast.js`
  - reuse `test/ufuzz.js` for code generation
  - use `acorn.parse()` for creating `estree`
- extend `test/ufuzz.js` for `acorn` workaround
  - catch variable redefinition
  - non-trivial literal as directive
  - adjust options for tolerance

Miscellaneous
- optional semi-colon when parsing directives

fixes #1914
closes #1915
2017-05-15 18:33:43 +08:00
Alex Lam S.L
a8c67ea353 fix bugs with getter/setter (#1926)
- `reduce_vars`
- `side_effects`
- property access for object
- `AST_SymbolAccessor` as key names

enhance `test/ufuzz.js`
- add object getter & setter
  - property assignment to setter
  - avoid infinite recursion in setter
- fix & adjust assignment operators
  - 50% `=`
  - 25% `+=`
  - 2.5% each for the rest
- avoid "Invalid array length"
- fix `console.log()`
  - bypass getter
  - curb recursive reference
- deprecate `-E`, always report runtime errors
2017-05-15 18:22:43 +08:00
olsonpm
d0b0aecfc5 document 3 max passes (#1928) 2017-05-15 18:18:38 +08:00
olsonpm
9f5a6029a3 clarify wording (#1931) 2017-05-15 18:18:27 +08:00
Alex Lam S.L
4027a0c962 fix parser bugs & CLI reporting (#1827)
fixes #1825
2017-05-15 18:18:04 +08:00
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
32 changed files with 1430 additions and 484 deletions

View File

@@ -11,9 +11,10 @@ There's also an
Chrome and probably Safari).
#### Note:
- release versions of `uglify-js` only support ECMAScript 5 (ES5). If you wish to minify
ES2015+ (ES6+) code then please use the [harmony](#harmony) development branch.
- Node 7 has a known performance regression and runs `uglify-js` twice as slow.
- `uglify-js` only supports ECMAScript 5 (ES5).
- Support for `const` is [present but incomplete](#support-for-const), and may not be
transformed properly.
- Those wishing to minify ES2015+ (ES6+) should use the `npm` package [**uglify-es**](https://github.com/mishoo/UglifyJS2/tree/harmony).
Install
-------
@@ -29,12 +30,6 @@ From NPM for programmatic use:
npm install uglify-js
From Git:
git clone git://github.com/mishoo/UglifyJS2.git
cd UglifyJS2
npm link .
Usage
-----
@@ -441,13 +436,19 @@ to set `true`; it's effectively a shortcut for `foo=true`).
compressor from discarding function names. Useful for code relying on
`Function.prototype.name`. See also: the `keep_fnames` [mangle option](#mangle).
- `passes` -- default `1`. Number of times to run compress. Use an
integer argument larger than 1 to further reduce code size in some cases.
Note: raising the number of passes will increase uglify compress time.
- `passes` -- default `1`. Number of times to run compress with a maximum of 3.
In some cases more than one pass leads to further compressed code. Keep in
mind more passes will take more time.
- `keep_infinity` -- default `false`. Pass `true` to prevent `Infinity` from
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 call is marked as "pure" if a comment annotation
`/*@__PURE__*/` or `/*#__PURE__*/` immediately precedes the call. For example:
`/*@__PURE__*/foo()`;
### The `unsafe` option
It enables some transformations that *might* break code logic in certain
@@ -983,19 +984,9 @@ The `source_map_options` (optional) can contain the following properties:
[compressor]: http://lisperator.net/uglifyjs/compress
[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)
branch to minify ES2015+ (ES6+) code please use the following in your `package.json` file:
```
"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.
`const` in `uglify-js@2.x` has function scope and as such behaves much like
`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.

View File

@@ -367,19 +367,19 @@ var index = 0;
if (ex instanceof UglifyJS.JS_Parse_Error) {
print_error("Parse error at " + file + ":" + ex.line + "," + ex.col);
var col = ex.col;
var line = code.split(/\r?\n/)[ex.line - (col ? 1 : 2)];
var lines = code.split(/\r?\n/);
var line = lines[ex.line - 1];
if (!line && !col) {
line = lines[ex.line - 2];
col = line.length;
}
if (line) {
if (col > 40) {
line = line.slice(col - 40);
col = 40;
}
if (col) {
print_error(line.slice(0, 80));
print_error(line.slice(0, col).replace(/\S/g, " ") + "^");
} else {
print_error(line.slice(-40));
print_error(line.slice(-40).replace(/\S/g, " ") + "^");
}
print_error(line.slice(0, 80));
print_error(line.slice(0, col).replace(/\S/g, " ") + "^");
}
print_error(ex.stack);
process.exit(1);

View File

@@ -214,12 +214,13 @@ var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", {
clone: function(deep) {
var node = this._clone(deep);
if (deep) {
var refs = node.label.references;
var label = this.label;
var label = node.label;
var def = this.label;
node.walk(new TreeWalker(function(node) {
if (node instanceof AST_LoopControl
&& node.label && node.label.thedef === label) {
refs.push(node);
&& node.label && node.label.thedef === def) {
node.label.thedef = label;
label.references.push(node);
}
}));
}
@@ -797,8 +798,8 @@ var AST_Object = DEFNODE("Object", "properties", {
var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", {
$documentation: "Base class for literal object properties",
$propdoc: {
key: "[string] the property name converted to a string for ObjectKeyVal. For setters and getters this is an arbitrary AST_Node.",
value: "[AST_Node] property value. For setters and getters this is an AST_Function."
key: "[string] the property name converted to a string for ObjectKeyVal. For setters and getters this is an AST_SymbolAccessor.",
value: "[AST_Node] property value. For setters and getters this is an AST_Accessor."
},
_walk: function(visitor) {
return visitor._visit(this, function(){

View File

@@ -271,6 +271,14 @@ merge(Compressor.prototype, {
if (d.fixed === undefined || !is_safe(d)
|| is_modified(node, 0, node.fixed_value() instanceof AST_Lambda)) {
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 (node instanceof AST_SymbolCatch) {
@@ -308,21 +316,55 @@ merge(Compressor.prototype, {
safe_ids = save_ids;
return true;
}
var iife;
if (node instanceof AST_Function
&& !node.name
&& (iife = tw.parent()) instanceof AST_Call
&& iife.expression === node) {
// Virtually turn IIFE parameters into variable definitions:
// (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})()
// So existing transformation rules can work on them.
node.argnames.forEach(function(arg, i) {
var d = arg.definition();
d.fixed = function() {
return iife.args[i] || make_node(AST_Undefined, iife);
};
mark(d, true);
});
if (node instanceof AST_Function) {
push();
var iife;
if (!node.name
&& (iife = tw.parent()) instanceof AST_Call
&& iife.expression === node) {
// Virtually turn IIFE parameters into variable definitions:
// (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})()
// So existing transformation rules can work on them.
node.argnames.forEach(function(arg, i) {
var d = arg.definition();
if (!node.uses_arguments && d.fixed === undefined) {
d.fixed = function() {
return iife.args[i] || make_node(AST_Undefined, iife);
};
mark(d, true);
} else {
d.fixed = false;
}
});
}
descend();
pop();
return true;
}
if (node instanceof AST_Accessor) {
var save_ids = safe_ids;
safe_ids = Object.create(null);
descend();
safe_ids = save_ids;
return true;
}
if (node instanceof AST_Binary
&& (node.operator == "&&" || node.operator == "||")) {
node.left.walk(tw);
push();
node.right.walk(tw);
pop();
return true;
}
if (node instanceof AST_Conditional) {
node.condition.walk(tw);
push();
node.consequent.walk(tw);
pop();
push();
node.alternative.walk(tw);
pop();
return true;
}
if (node instanceof AST_If || node instanceof AST_DWLoop) {
node.condition.walk(tw);
@@ -359,7 +401,19 @@ merge(Compressor.prototype, {
pop();
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();
descend();
pop();
@@ -393,7 +447,10 @@ merge(Compressor.prototype, {
}
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;
} else {
def.fixed = false;
@@ -419,6 +476,14 @@ merge(Compressor.prototype, {
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) {
var scope, i = 0;
while (scope = compressor.parent(i++)) {
@@ -1160,12 +1225,12 @@ merge(Compressor.prototype, {
&& !node.expression.has_side_effects(compressor);
}
// may_eq_null()
// returns true if this node may evaluate to null or undefined
// may_throw_on_access()
// returns true if this node may be null, undefined or contain `AST_Accessor`
(function(def) {
AST_Node.DEFMETHOD("may_eq_null", function(compressor) {
AST_Node.DEFMETHOD("may_throw_on_access", function(compressor) {
var pure_getters = compressor.option("pure_getters");
return !pure_getters || this._eq_null(pure_getters);
return !pure_getters || this._throw_on_access(pure_getters);
});
function is_strict(pure_getters) {
@@ -1177,7 +1242,12 @@ merge(Compressor.prototype, {
def(AST_Undefined, return_true);
def(AST_Constant, return_false);
def(AST_Array, return_false);
def(AST_Object, return_false);
def(AST_Object, function(pure_getters) {
if (!is_strict(pure_getters)) return false;
for (var i = this.properties.length; --i >=0;)
if (this.properties[i].value instanceof AST_Accessor) return true;
return false;
});
def(AST_Function, return_false);
def(AST_UnaryPostfix, return_false);
def(AST_UnaryPrefix, function() {
@@ -1186,33 +1256,33 @@ merge(Compressor.prototype, {
def(AST_Binary, function(pure_getters) {
switch (this.operator) {
case "&&":
return this.left._eq_null(pure_getters);
return this.left._throw_on_access(pure_getters);
case "||":
return this.left._eq_null(pure_getters)
&& this.right._eq_null(pure_getters);
return this.left._throw_on_access(pure_getters)
&& this.right._throw_on_access(pure_getters);
default:
return false;
}
})
def(AST_Assign, function(pure_getters) {
return this.operator == "="
&& this.right._eq_null(pure_getters);
&& this.right._throw_on_access(pure_getters);
})
def(AST_Conditional, function(pure_getters) {
return this.consequent._eq_null(pure_getters)
|| this.alternative._eq_null(pure_getters);
return this.consequent._throw_on_access(pure_getters)
|| this.alternative._throw_on_access(pure_getters);
})
def(AST_Seq, function(pure_getters) {
return this.cdr._eq_null(pure_getters);
return this.cdr._throw_on_access(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);
return !fixed || fixed._throw_on_access(pure_getters);
});
})(function(node, func) {
node.DEFMETHOD("_eq_null", func);
node.DEFMETHOD("_throw_on_access", func);
});
/* -----[ boolean/negation helpers ]----- */
@@ -1549,23 +1619,20 @@ merge(Compressor.prototype, {
: ev(this.alternative, compressor);
});
def(AST_SymbolRef, function(compressor){
if (this._evaluating) throw def;
if (!compressor.option("reduce_vars") || this._evaluating) throw def;
this._evaluating = true;
try {
var fixed = this.fixed_value();
if (compressor.option("reduce_vars") && fixed) {
if (compressor.option("unsafe")) {
if (!HOP(fixed, "_evaluated")) {
fixed._evaluated = ev(fixed, compressor);
}
return fixed._evaluated;
}
return ev(fixed, compressor);
}
if (!fixed) throw def;
var value = ev(fixed, compressor);
if (!HOP(fixed, "_eval")) fixed._eval = function() {
return value;
};
if (value && typeof value == "object" && this.definition().escaped) throw def;
return value;
} finally {
this._evaluating = false;
}
throw def;
});
def(AST_PropAccess, function(compressor){
if (compressor.option("unsafe")) {
@@ -1755,11 +1822,11 @@ merge(Compressor.prototype, {
return any(this.elements, compressor);
});
def(AST_Dot, function(compressor){
return this.expression.may_eq_null(compressor)
return this.expression.may_throw_on_access(compressor)
|| this.expression.has_side_effects(compressor);
});
def(AST_Sub, function(compressor){
return this.expression.may_eq_null(compressor)
return this.expression.may_throw_on_access(compressor)
|| this.expression.has_side_effects(compressor)
|| this.property.has_side_effects(compressor);
});
@@ -1891,6 +1958,7 @@ merge(Compressor.prototype, {
&& node instanceof AST_Assign
&& node.operator == "="
&& node.left instanceof AST_SymbolRef
&& !is_reference_const(node.left)
&& scope === self) {
node.right.walk(tw);
return true;
@@ -1980,7 +2048,7 @@ merge(Compressor.prototype, {
}
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){
if (def.value) def.value = def.value.transform(tt);
var sym = def.name.definition();
@@ -2058,26 +2126,32 @@ merge(Compressor.prototype, {
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) {
descend(node, this);
if (node.init instanceof AST_BlockStatement) {
// certain combination of unused name + side effect leads to:
// https://github.com/mishoo/UglifyJS2/issues/44
// that's an invalid AST.
// We fix it at this stage by moving the `var` outside the `for`.
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
});
var block = node.init;
node.init = block.body.pop();
block.body.push(node);
return in_list ? MAP.splice(block.body) : block;
} else if (is_empty(node.init)) {
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)
return node;
@@ -2256,6 +2330,7 @@ merge(Compressor.prototype, {
var args = trim(this.args, compressor, first_in_statement);
return args && AST_Seq.from_array(args);
});
def(AST_Accessor, return_null);
def(AST_Function, return_null);
def(AST_Binary, function(compressor, first_in_statement){
var right = this.right.drop_side_effect_free(compressor);
@@ -2326,11 +2401,11 @@ merge(Compressor.prototype, {
return values && AST_Seq.from_array(values);
});
def(AST_Dot, function(compressor, first_in_statement){
if (this.expression.may_eq_null(compressor)) return this;
if (this.expression.may_throw_on_access(compressor)) return this;
return this.expression.drop_side_effect_free(compressor, first_in_statement);
});
def(AST_Sub, function(compressor, first_in_statement){
if (this.expression.may_eq_null(compressor)) return this;
if (this.expression.may_throw_on_access(compressor)) return this;
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);
var property = this.property.drop_side_effect_free(compressor);
@@ -2380,7 +2455,7 @@ merge(Compressor.prototype, {
if (compressor.option("dead_code") && self instanceof AST_While) {
var 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 (self instanceof AST_Do) {
var has_loop_control = false;
@@ -2389,7 +2464,8 @@ merge(Compressor.prototype, {
if (node instanceof AST_LoopControl && tw.loopcontrol_target(node) === self)
return has_loop_control = true;
});
self.walk(tw);
var parent = compressor.parent();
(parent instanceof AST_LabeledStatement ? parent : self).walk(tw);
if (!has_loop_control) return self.body;
}
}
@@ -2459,7 +2535,7 @@ merge(Compressor.prototype, {
}));
}
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) {
cond = make_node_from_constant(cond, self.condition).transform(compressor);
@@ -2711,9 +2787,9 @@ merge(Compressor.prototype, {
var body = [];
if (self.bcatch) extract_declarations_from_unreachable_code(compressor, self.bcatch, 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
}).optimize(compressor) : make_node(AST_EmptyStatement, self);
}).optimize(compressor);
}
return self;
});
@@ -3041,7 +3117,8 @@ merge(Compressor.prototype, {
}
if (left
&& !(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 cdr = self.cdr;
while (true) {
@@ -3595,7 +3672,9 @@ merge(Compressor.prototype, {
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 fixed = self.fixed_value();
if (fixed) {
@@ -3603,21 +3682,45 @@ merge(Compressor.prototype, {
var init = fixed.evaluate(compressor);
if (init !== fixed) {
init = make_node_from_constant(init, fixed);
var value = best_of_expression(init.optimize(compressor), fixed).print_to_string().length;
var value = init.optimize(compressor).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 freq = d.references.length;
var overhead = d.global || !freq ? 0 : (name + 2 + value) / freq;
d.should_replace = value <= name + overhead ? init : false;
var overhead = 0;
if (compressor.option("unused") && (!d.global || compressor.option("toplevel"))) {
overhead = (name + 2 + value) / d.references.length;
}
d.should_replace = value <= name + overhead ? fn : false;
} else {
d.should_replace = false;
}
}
if (d.should_replace) {
return best_of_expression(d.should_replace.optimize(compressor), fixed).clone(true);
return d.should_replace();
}
}
}
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) {

View File

@@ -111,23 +111,19 @@
},
Property: function(M) {
var key = M.key;
var name = key.type == "Identifier" ? key.name : key.value;
var args = {
start : my_start_token(key),
end : my_end_token(M.value),
key : name,
key : key.type == "Identifier" ? key.name : key.value,
value : from_moz(M.value)
};
switch (M.kind) {
case "init":
return new AST_ObjectKeyVal(args);
case "set":
args.value.name = from_moz(key);
return new AST_ObjectSetter(args);
case "get":
args.value.name = from_moz(key);
return new AST_ObjectGetter(args);
}
if (M.kind == "init") return new AST_ObjectKeyVal(args);
args.key = new AST_SymbolAccessor({
name: args.key
});
args.value = new AST_Accessor(args.value);
if (M.kind == "get") return new AST_ObjectGetter(args);
if (M.kind == "set") return new AST_ObjectSetter(args);
},
ArrayExpression: function(M) {
return new AST_Array({
@@ -256,10 +252,7 @@
map("CallExpression", AST_Call, "callee>expression, arguments@args");
def_to_moz(AST_Toplevel, function To_Moz_Program(M) {
return {
type: "Program",
body: M.body.map(to_moz)
};
return to_moz_scope("Program", M);
});
def_to_moz(AST_Defun, function To_Moz_FunctionDeclaration(M) {
@@ -267,7 +260,7 @@
type: "FunctionDeclaration",
id: to_moz(M.name),
params: M.argnames.map(to_moz),
body: to_moz_block(M)
body: to_moz_scope("BlockStatement", M)
}
});
@@ -276,7 +269,7 @@
type: "FunctionExpression",
id: to_moz(M.name),
params: M.argnames.map(to_moz),
body: to_moz_block(M)
body: to_moz_scope("BlockStatement", M)
}
});
@@ -382,11 +375,10 @@
});
def_to_moz(AST_ObjectProperty, function To_Moz_Property(M) {
var key = (
is_identifier(M.key)
? {type: "Identifier", name: M.key}
: {type: "Literal", value: M.key}
);
var key = {
type: "Literal",
value: M.key instanceof AST_SymbolAccessor ? M.key.name : M.key
};
var kind;
if (M instanceof AST_ObjectKeyVal) {
kind = "init";
@@ -547,8 +539,8 @@
moz_to_me = new Function("U2", "my_start_token", "my_end_token", "from_moz", "return(" + moz_to_me + ")")(
exports, my_start_token, my_end_token, from_moz
);
me_to_moz = new Function("to_moz", "to_moz_block", "return(" + me_to_moz + ")")(
to_moz, to_moz_block
me_to_moz = new Function("to_moz", "to_moz_block", "to_moz_scope", "return(" + me_to_moz + ")")(
to_moz, to_moz_block, to_moz_scope
);
MOZ_TO_ME[moztype] = moz_to_me;
def_to_moz(mytype, me_to_moz);
@@ -606,4 +598,14 @@
};
};
function to_moz_scope(type, node) {
var body = node.body.map(to_moz);
if (node.body[0] instanceof AST_SimpleStatement && node.body[0].body instanceof AST_String) {
body.unshift(to_moz(new AST_EmptyStatement(node.body[0])));
}
return {
type: type,
body: body
};
};
})();

View File

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

View File

@@ -111,7 +111,7 @@ var WHITESPACE_CHARS = makePredicate(characters(" \u00a0\n\r\t\f\u000b\u200b\u20
var NEWLINE_CHARS = makePredicate(characters("\n\r\u2028\u2029"));
var PUNC_BEFORE_EXPRESSION = makePredicate(characters("[{(,.;:"));
var PUNC_BEFORE_EXPRESSION = makePredicate(characters("[{(,;:"));
var PUNC_CHARS = makePredicate(characters("[]{}(),;:"));
@@ -807,24 +807,20 @@ function parse($TEXT, options) {
handle_regexp();
switch (S.token.type) {
case "string":
var dir = false;
if (S.in_directives === true) {
if ((is_token(peek(), "punc", ";") || peek().nlb) && S.token.raw.indexOf("\\") === -1) {
if (S.in_directives) {
tmp = peek();
if (S.token.raw.indexOf("\\") == -1
&& (tmp.nlb
|| is_token(tmp, "eof")
|| is_token(tmp, "punc", ";")
|| is_token(tmp, "punc", "}"))) {
S.input.add_directive(S.token.value);
} else {
S.in_directives = false;
}
}
var dir = S.in_directives, stat = simple_statement();
if (dir) {
return new AST_Directive({
start : stat.body.start,
end : stat.body.end,
quote : stat.body.quote,
value : stat.body.value,
});
}
return stat;
return dir ? new AST_Directive(stat.body) : stat;
case "num":
case "regexp":
case "operator":
@@ -1320,10 +1316,15 @@ function parse($TEXT, options) {
var type = start.type;
var name = as_property_name();
if (type == "name" && !is("punc", ":")) {
var key = new AST_SymbolAccessor({
start: S.token,
name: as_property_name(),
end: prev()
});
if (name == "get") {
a.push(new AST_ObjectGetter({
start : start,
key : as_atom_node(),
key : key,
value : create_accessor(),
end : prev()
}));
@@ -1332,7 +1333,7 @@ function parse($TEXT, options) {
if (name == "set") {
a.push(new AST_ObjectSetter({
start : start,
key : as_atom_node(),
key : key,
value : create_accessor(),
end : prev()
}));
@@ -1354,14 +1355,15 @@ function parse($TEXT, options) {
function as_property_name() {
var tmp = S.token;
next();
switch (tmp.type) {
case "operator":
if (!KEYWORDS(tmp.value)) unexpected();
case "num":
case "string":
case "name":
case "operator":
case "keyword":
case "atom":
next();
return tmp.value;
default:
unexpected();
@@ -1370,16 +1372,9 @@ function parse($TEXT, options) {
function as_name() {
var tmp = S.token;
if (tmp.type != "name") unexpected();
next();
switch (tmp.type) {
case "name":
case "operator":
case "keyword":
case "atom":
return tmp.value;
default:
unexpected();
}
return tmp.value;
};
function _make_symbol(type) {
@@ -1440,14 +1435,14 @@ function parse($TEXT, options) {
if (is("operator") && UNARY_PREFIX(start.value)) {
next();
handle_regexp();
var ex = make_unary(AST_UnaryPrefix, start.value, maybe_unary(allow_calls));
var ex = make_unary(AST_UnaryPrefix, start, maybe_unary(allow_calls));
ex.start = start;
ex.end = prev();
return ex;
}
var val = expr_atom(allow_calls);
while (is("operator") && UNARY_POSTFIX(S.token.value) && !S.token.nlb) {
val = make_unary(AST_UnaryPostfix, S.token.value, val);
val = make_unary(AST_UnaryPostfix, S.token, val);
val.start = start;
val.end = S.token;
next();
@@ -1455,9 +1450,10 @@ function parse($TEXT, options) {
return val;
};
function make_unary(ctor, op, expr) {
function make_unary(ctor, token, expr) {
var op = token.value;
if ((op == "++" || op == "--") && !is_assignable(expr))
croak("Invalid use of " + op + " operator", null, ctor === AST_UnaryPrefix ? expr.start.col - 1 : null);
croak("Invalid use of " + op + " operator", token.line, token.col, token.pos);
return new ctor({ operator: op, expression: expr });
};

View File

@@ -361,11 +361,6 @@ AST_Symbol.DEFMETHOD("unmangleable", function(options){
return this.definition().unmangleable(options);
});
// property accessors are not mangleable
AST_SymbolAccessor.DEFMETHOD("unmangleable", function(){
return true;
});
// labels are always mangleable
AST_Label.DEFMETHOD("unmangleable", function(){
return false;

View File

@@ -4,7 +4,7 @@
"homepage": "http://lisperator.net/uglifyjs",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause",
"version": "2.8.22",
"version": "2.8.25",
"engines": {
"node": ">=0.8.0"
},
@@ -33,10 +33,7 @@
"yargs": "~3.10.0"
},
"devDependencies": {
"acorn": "~0.6.0",
"escodegen": "~1.3.3",
"esfuzz": "~0.3.1",
"estraverse": "~1.5.1",
"acorn": "~5.0.3",
"mocha": "~2.3.4"
},
"optionalDependencies": {

View File

@@ -11,8 +11,8 @@ if (!args.length) {
}
args.push("--stats");
var urls = [
"https://code.jquery.com/jquery-3.1.1.js",
"https://code.angularjs.org/1.6.1/angular.js",
"https://code.jquery.com/jquery-3.2.1.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://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.js",
"https://unpkg.com/react@15.3.2/dist/react.js",

View File

@@ -1592,3 +1592,49 @@ var_side_effects_3: {
}
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

@@ -256,3 +256,19 @@ try_catch_finally: {
"1",
]
}
accessor: {
options = {
side_effects: true,
}
input: {
({
get a() {},
set a(v){
this.b = 2;
},
b: 1
});
}
expect: {}
}

View File

@@ -1029,3 +1029,63 @@ delete_assign_2: {
}
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

@@ -93,3 +93,57 @@ issue_485_crashing_1530: {
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

@@ -35,11 +35,11 @@ f7: {
console.log(a, b);
}
expect_exact: [
"var a = 100, b = 10;",
"var b = 10;",
"",
"!function() {",
" for (;b = a, !1; ) ;",
"}(), console.log(a, b);",
" for (;b = 100, !1; ) ;",
"}(), console.log(100, b);",
]
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

@@ -119,3 +119,62 @@ chained: {
a.b.c;
}
}
impure_getter_1: {
options = {
pure_getters: "strict",
side_effects: true,
}
input: {
({
get a() {
console.log(1);
},
b: 1
}).a;
({
get a() {
console.log(1);
},
b: 1
}).b;
}
expect: {
({
get a() {
console.log(1);
},
b: 1
}).a;
({
get a() {
console.log(1);
},
b: 1
}).b;
}
expect_stdout: "1"
}
impure_getter_2: {
options = {
pure_getters: true,
side_effects: true,
}
input: {
// will produce incorrect output because getter is not pure
({
get a() {
console.log(1);
},
b: 1
}).a;
({
get a() {
console.log(1);
},
b: 1
}).b;
}
expect: {}
}

View File

@@ -41,22 +41,22 @@ reduce_vars: {
var A = 1;
(function() {
console.log(-3);
console.log(-4);
console.log(A - 5);
})();
(function f1() {
var a = 2;
console.log(-3);
console.log(a - 5);
eval("console.log(a);");
})();
(function f2(eval) {
var a = 2;
console.log(-3);
console.log(a - 5);
eval("console.log(a);");
})(eval);
(function() {
return "yes";
})();
console.log(2);
console.log(A + 1);
}
expect_stdout: true
}
@@ -300,7 +300,7 @@ unsafe_evaluate_array: {
}
}
unsafe_evaluate_equality: {
unsafe_evaluate_equality_1: {
options = {
evaluate : true,
reduce_vars : true,
@@ -308,47 +308,62 @@ unsafe_evaluate_equality: {
unused : true
}
input: {
function f0(){
function f0() {
var a = {};
console.log(a === a);
return a === a;
}
function f1(){
function f1() {
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 b = a;
var c = a;
console.log(b === c);
return b === c;
}
function f3(){
function f3() {
var a = [1, 2, 3];
var b = a;
var c = a;
console.log(b === c);
return b === c;
}
console.log(f2(), f3());
}
expect: {
function f0(){
console.log(true);
function f2() {
return true;
}
function f1(){
console.log(true);
}
function f2(){
console.log(true);
}
function f3(){
console.log(true);
function f3() {
return true;
}
console.log(f2(), f3());
}
expect_stdout: true
}
passes: {
@@ -1717,7 +1732,10 @@ redefine_arguments_3: {
console.log(function() {
var arguments;
return typeof arguments;
}(), "number", "undefined");
}(), "number", function(x) {
var arguments = x;
return typeof arguments;
}());
}
expect_stdout: "object number undefined"
}
@@ -1995,3 +2013,188 @@ catch_var: {
}
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"
}
accessor: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
}
input: {
var a = 1;
console.log({
get a() {
a = 2;
return a;
},
b: 1
}.b, a);
}
expect: {
var a = 1;
console.log({
get a() {
a = 2;
return a;
},
b: 1
}.b, a);
}
expect_stdout: "1 1"
}

View File

@@ -610,3 +610,27 @@ delete_seq_6: {
}
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

@@ -0,0 +1 @@
++null

View File

@@ -0,0 +1 @@
a.=

View File

@@ -0,0 +1 @@
%.a;

View File

@@ -0,0 +1 @@
a./();

View File

@@ -0,0 +1 @@
console.log({%: 1});

View File

@@ -16,7 +16,7 @@ describe("Accessor tokens", function() {
assert.equal(node.start.pos, 12);
assert.equal(node.end.endpos, 46);
assert(node.key instanceof UglifyJS.AST_SymbolRef);
assert(node.key instanceof UglifyJS.AST_SymbolAccessor);
assert.equal(node.key.start.pos, 16);
assert.equal(node.key.end.endpos, 22);

View File

@@ -298,12 +298,87 @@ describe("bin/uglifyjs", function () {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/assign_3.js:1,18",
"Parse error at test/input/invalid/assign_3.js:1,17",
"console.log(3 || ++this);",
" ^",
" ^",
"SyntaxError: Invalid use of ++ operator"
].join("\n"));
done();
});
});
it("Should throw syntax error (++null)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/assign_4.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/assign_4.js:1,0",
"++null",
"^",
"SyntaxError: Invalid use of ++ operator"
].join("\n"));
done();
});
});
it("Should throw syntax error (a.=)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/dot_1.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/dot_1.js:1,2",
"a.=",
" ^",
"SyntaxError: Unexpected token: operator (=)"
].join("\n"));
done();
});
});
it("Should throw syntax error (%.a)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/dot_2.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/dot_2.js:1,0",
"%.a;",
"^",
"SyntaxError: Unexpected token: operator (%)"
].join("\n"));
done();
});
});
it("Should throw syntax error (a./();)", function(done) {
var command = uglifyjscmd + ' test/input/invalid/dot_3.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/dot_3.js:1,2",
"a./();",
" ^",
"SyntaxError: Unexpected token: operator (/)"
].join("\n"));
done();
});
});
it("Should throw syntax error ({%: 1})", function(done) {
var command = uglifyjscmd + ' test/input/invalid/object.js';
exec(command, function (err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stdout, "");
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
"Parse error at test/input/invalid/object.js:1,13",
"console.log({%: 1});",
" ^",
"SyntaxError: Unexpected token: operator (%)"
].join("\n"));
done();
});
});
});

View File

@@ -351,18 +351,28 @@ describe("Directives", function() {
var tests = [
[
'"use strict";"use strict";"use strict";"use foo";"use strict";;"use sloppy";doSomething("foo");',
'"use strict";"use foo";doSomething("foo");'
'"use strict";"use foo";doSomething("foo");',
'function f(){ "use strict" }',
'function f(){ "use asm" }',
'function f(){ "use nondirective" }',
'function f(){ ;"use strict" }',
'function f(){ "use \n"; }',
],
[
// Nothing gets optimised in the compressor because "use asm" is the first statement
'"use asm";"use\\x20strict";1+1;',
'"use asm";;"use strict";1+1;' // Yet, the parser noticed that "use strict" wasn't a directive
'"use asm";;"use strict";1+1;', // Yet, the parser noticed that "use strict" wasn't a directive
'function f(){"use strict"}',
'function f(){"use asm"}',
'function f(){"use nondirective"}',
'function f(){}',
'function f(){}',
]
];
for (var i = 0; i < tests.length; i++) {
assert.strictEqual(
uglify.minify(tests[i][0], {fromString: true, compress: {collapse_vars: true, side_effects: true}}).code,
uglify.minify(tests[i][0], {fromString: true}).code,
tests[i][1],
tests[i][0]
);

View File

@@ -71,7 +71,7 @@ describe("Getters and setters", function() {
var fail = function(data) {
return function (e) {
return e instanceof UglifyJS.JS_Parse_Error &&
e.message === "Invalid getter/setter name: " + data.operator;
e.message === "Unexpected token: operator (" + data.operator + ")";
};
};

View File

@@ -1,103 +1,87 @@
// Testing UglifyJS <-> SpiderMonkey AST conversion
// through generative testing.
"use strict";
var UglifyJS = require(".."),
escodegen = require("escodegen"),
esfuzz = require("esfuzz"),
estraverse = require("estraverse"),
prefix = "\r ";
var acorn = require("acorn");
var ufuzz = require("./ufuzz");
var UglifyJS = require("..");
// Normalizes input AST for UglifyJS in order to get correct comparison.
function normalizeInput(ast) {
return estraverse.replace(ast, {
enter: function(node, parent) {
switch (node.type) {
// Internally mark all the properties with semi-standard type "Property".
case "ObjectExpression":
node.properties.forEach(function (property) {
property.type = "Property";
});
break;
// Since UglifyJS doesn"t recognize different types of property keys,
// decision on SpiderMonkey node type is based on check whether key
// can be valid identifier or not - so we do in input AST.
case "Property":
var key = node.key;
if (key.type === "Literal" && typeof key.value === "string" && UglifyJS.is_identifier(key.value)) {
node.key = {
type: "Identifier",
name: key.value
};
} else if (key.type === "Identifier" && !UglifyJS.is_identifier(key.name)) {
node.key = {
type: "Literal",
value: key.name
};
}
break;
// UglifyJS internally flattens all the expression sequences - either
// to one element (if sequence contains only one element) or flat list.
case "SequenceExpression":
node.expressions = node.expressions.reduce(function flatten(list, expr) {
return list.concat(expr.type === "SequenceExpression" ? expr.expressions.reduce(flatten, []) : [expr]);
}, []);
if (node.expressions.length === 1) {
return node.expressions[0];
}
break;
}
}
});
}
module.exports = function(options) {
console.log("--- UglifyJS <-> Mozilla AST conversion");
for (var counter = 0; counter < options.iterations; counter++) {
process.stdout.write(prefix + counter + "/" + options.iterations);
var ast1 = normalizeInput(esfuzz.generate({
maxDepth: options.maxDepth
}));
var ast2 =
UglifyJS
.AST_Node
.from_mozilla_ast(ast1)
.to_mozilla_ast();
var astPair = [
{name: 'expected', value: ast1},
{name: 'actual', value: ast2}
];
var jsPair = astPair.map(function(item) {
return {
name: item.name,
value: escodegen.generate(item.value)
function try_beautify(code) {
var beautified;
try {
beautified = UglifyJS.minify(code, {
fromString: true,
compress: false,
mangle: false,
output: {
beautify: true,
bracketize: true
}
});
if (jsPair[0].value !== jsPair[1].value) {
var fs = require("fs");
var acorn = require("acorn");
fs.existsSync("tmp") || fs.mkdirSync("tmp");
jsPair.forEach(function (item) {
var fileName = "tmp/dump_" + item.name;
var ast = acorn.parse(item.value);
fs.writeFileSync(fileName + ".js", item.value);
fs.writeFileSync(fileName + ".json", JSON.stringify(ast, null, 2));
});
process.stdout.write("\n");
throw new Error("Got different outputs, check out tmp/dump_*.{js,json} for codes and ASTs.");
}
} catch (ex) {
beautified = { error: ex };
}
if (beautified.error) {
console.log("// !!! beautify failed !!!");
console.log(beautified.error.stack);
console.log(code);
} else {
console.log("// (beautified)");
console.log(beautified.code);
}
}
process.stdout.write(prefix + "Probability of error is less than " + (100 / options.iterations) + "%, stopping.\n");
};
function test(original, estree, description) {
var transformed;
try {
transformed = UglifyJS.minify(estree, {
fromString: true,
compress: false,
mangle: false,
spidermonkey: true
});
} catch (ex) {
transformed = { error: ex };
}
if (transformed.error || original !== transformed.code) {
console.log("//=============================================================");
console.log("// !!!!!! Failed... round", round);
console.log("// original code");
try_beautify(original);
console.log();
console.log();
console.log("//-------------------------------------------------------------");
console.log("//", description);
if (transformed.error) {
console.log(transformed.error.stack);
} else {
try_beautify(transformed.code);
}
console.log("!!!!!! Failed... round", round);
process.exit(1);
}
}
var num_iterations = ufuzz.num_iterations;
for (var round = 1; round <= num_iterations; round++) {
process.stdout.write(round + " of " + num_iterations + "\r");
var code = ufuzz.createTopLevelCode();
var uglified = {
ast: UglifyJS.parse(code),
code: UglifyJS.minify(code, {
fromString: true,
compress: false,
mangle: false
}).code
};
test(uglified.code, uglified.ast.to_mozilla_ast(), "AST_Node.to_mozilla_ast()");
try {
test(uglified.code, acorn.parse(code), "acorn.parse()");
} catch (e) {
console.log("//=============================================================");
console.log("// acorn parser failed... round", round);
console.log(e);
console.log("// original code");
console.log(code);
}
}
console.log();

View File

@@ -23,12 +23,6 @@ mocha_tests();
var run_sourcemaps_tests = require('./sourcemaps');
run_sourcemaps_tests();
var run_ast_conversion_tests = require("./mozilla-ast");
run_ast_conversion_tests({
iterations: 1000
});
/* -----[ utils ]----- */
function tmpl() {

View File

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

View File

@@ -2,7 +2,7 @@
// derived from https://github.com/qfox/uglyfuzzer by Peter van der Zee
"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
// 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 INTERVAL_COUNT = 100;
var STMT_BLOCK = 0;
var STMT_IF_ELSE = 1;
var STMT_DO_WHILE = 2;
var STMT_WHILE = 3;
var STMT_FOR_LOOP = 4;
var STMT_SEMI = 5;
var STMT_EXPR = 6;
var STMT_SWITCH = 7;
var STMT_VAR = 8;
var STMT_RETURN_ETC = 9;
var STMT_FUNC_EXPR = 10;
var STMT_TRY = 11;
var STMT_C = 12;
var STMTS_TO_USE = [
STMT_BLOCK,
STMT_IF_ELSE,
STMT_DO_WHILE,
STMT_WHILE,
STMT_FOR_LOOP,
STMT_SEMI,
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_ARG_TO_ID = Object.create(null);
var STMTS_TO_USE = [];
function STMT_(name) {
return STMT_ARG_TO_ID[name] = STMTS_TO_USE.push(STMTS_TO_USE.length) - 1;
}
var STMT_BLOCK = STMT_("block");
var STMT_IF_ELSE = STMT_("ifelse");
var STMT_DO_WHILE = STMT_("dowhile");
var STMT_WHILE = STMT_("while");
var STMT_FOR_LOOP = STMT_("forloop");
var STMT_FOR_IN = STMT_("forin");
var STMT_SEMI = STMT_("semi");
var STMT_EXPR = STMT_("expr");
var STMT_SWITCH = STMT_("switch");
var STMT_VAR = STMT_("var");
var STMT_RETURN_ETC = STMT_("stop");
var STMT_FUNC_EXPR = STMT_("funcexpr");
var STMT_TRY = STMT_("try");
var STMT_C = STMT_("c");
var STMT_FIRST_LEVEL_OVERRIDE = -1;
var STMT_SECOND_LEVEL_OVERRIDE = -1;
@@ -71,7 +48,9 @@ var STMT_COUNT_FROM_GLOBAL = true; // count statement depth from nearest functio
var num_iterations = +process.argv[2] || 1/0;
var verbose = false; // log every generated test
var verbose_interval = false; // log every 100 generated tests
var verbose_error = false;
var use_strict = false;
var catch_redef = require.main === module;
var generate_directive = require.main === module;
for (var i = 2; i < process.argv.length; ++i) {
switch (process.argv[i]) {
case '-v':
@@ -80,9 +59,6 @@ for (var i = 2; i < process.argv.length; ++i) {
case '-V':
verbose_interval = true;
break;
case '-E':
verbose_error = true;
break;
case '-t':
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');
@@ -101,6 +77,15 @@ for (var i = 2; i < process.argv.length; ++i) {
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');
break;
case '--no-catch-redef':
catch_redef = false;
break;
case '--no-directive':
generate_directive = false;
break;
case '--use-strict':
use_strict = true;
break;
case '--stmt-depth-from-func':
STMT_COUNT_FROM_GLOBAL = false;
break;
@@ -122,11 +107,13 @@ for (var i = 2; i < process.argv.length; ++i) {
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 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('-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('-s2 <statement name>: force the second level statement to be this one (see list below)');
console.log('--no-catch-redef: do not redefine catch variables');
console.log('--no-directive: do not generate directives');
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('--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');
@@ -210,12 +197,33 @@ var ASSIGNMENTS = [
'=',
'=',
'=',
'=',
'=',
'=',
'=',
'=',
'=',
'=',
'=',
'=',
'=',
'=',
'=',
'=',
'=',
'==',
'!=',
'===',
'!==',
'+=',
'+=',
'+=',
'+=',
'+=',
'+=',
'+=',
'+=',
'+=',
'+=',
'-=',
'*=',
'/=',
@@ -225,7 +233,8 @@ var ASSIGNMENTS = [
'<<=',
'>>=',
'>>>=',
'%=' ];
'%=',
];
var UNARY_SAFE = [
'+',
@@ -294,17 +303,33 @@ var TYPEOF_OUTCOMES = [
'symbol',
'crap' ];
var unique_vars = [];
var loops = 0;
var funcs = 0;
var labels = 10000;
function rng(max) {
var r = randomBytes(2).readUInt16LE(0) / 65536;
return Math.floor(max * r);
}
function strictMode() {
return use_strict && rng(4) == 0 ? '"use strict";' : '';
}
function createTopLevelCode() {
if (rng(2) === 0) return createStatements(3, MAX_GENERATION_RECURSION_DEPTH, CANNOT_THROW, CANNOT_BREAK, CANNOT_CONTINUE, CANNOT_RETURN, 0);
return createFunctions(rng(MAX_GENERATED_TOPLEVELS_PER_RUN) + 1, MAX_GENERATION_RECURSION_DEPTH, IN_GLOBAL, ANY_TYPE, CANNOT_THROW, 0);
VAR_NAMES.length = INITIAL_NAMES_LEN; // prune any previous names still in the list
unique_vars.length = 0;
loops = 0;
funcs = 0;
return [
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) {
@@ -332,21 +357,36 @@ function createArgs() {
return args.join(', ');
}
function filterDirective(s) {
if (!generate_directive && !s[1] && /\("/.test(s[2])) s[2] = ';' + s[2];
return s;
}
function createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) {
if (--recurmax < 0) { return ';'; }
if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0;
var func = funcs++;
var namesLenBefore = VAR_NAMES.length;
var name = (inGlobal || rng(5) > 0) ? 'f' + func : createVarName(MANDATORY, noDecl);
if (name === 'a' || name === 'b' || name === 'c') name = 'f' + func; // quick hack to prevent assignment to func names of being called
var s = '';
var name;
if (inGlobal || rng(5) > 0) name = 'f' + func;
else {
unique_vars.push('a', 'b', 'c');
name = createVarName(MANDATORY, noDecl);
unique_vars.length -= 3;
}
var s = [
'function ' + name + '(' + createParams() + '){',
strictMode()
];
if (rng(5) === 0) {
// functions with functions. lower the recursion to prevent a mess.
s = 'function ' + name + '(' + createParams() + '){' + createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), NOT_GLOBAL, ANY_TYPE, canThrow, stmtDepth) + '}\n';
s.push(createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), NOT_GLOBAL, ANY_TYPE, canThrow, stmtDepth));
} else {
// functions with statements
s = 'function ' + name + '(' + createParams() + '){' + createStatements(3, recurmax, canThrow, CANNOT_THROW, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + '}\n';
s.push(createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth));
}
s.push('}', '');
s = filterDirective(s).join('\n');
VAR_NAMES.length = namesLenBefore;
@@ -354,7 +394,6 @@ function createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) {
// avoid "function statements" (decl inside statements)
else if (inGlobal || rng(10) > 0) s += 'var ' + createVarName(MANDATORY) + ' = ' + name + '(' + createArgs() + ');';
return s;
}
@@ -367,7 +406,41 @@ function createStatements(n, recurmax, canThrow, canBreak, canContinue, cannotRe
return s;
}
function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) {
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, target) {
++stmtDepth;
var loop = ++loops;
if (--recurmax < 0) {
@@ -375,24 +448,44 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
}
// allow to forcefully generate certain structures at first or second recursion level
var target = 0;
if (stmtDepth === 1 && STMT_FIRST_LEVEL_OVERRIDE >= 0) target = STMT_FIRST_LEVEL_OVERRIDE;
else if (stmtDepth === 2 && STMT_SECOND_LEVEL_OVERRIDE >= 0) target = STMT_SECOND_LEVEL_OVERRIDE;
else target = STMTS_TO_USE[rng(STMTS_TO_USE.length)];
if (target === undefined) {
if (stmtDepth === 1 && STMT_FIRST_LEVEL_OVERRIDE >= 0) target = STMT_FIRST_LEVEL_OVERRIDE;
else if (stmtDepth === 2 && STMT_SECOND_LEVEL_OVERRIDE >= 0) target = STMT_SECOND_LEVEL_OVERRIDE;
else target = STMTS_TO_USE[rng(STMTS_TO_USE.length)];
}
switch (target) {
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:
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:
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:
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:
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:
return ';';
return use_strict && rng(20) === 0 ? '"use strict";' : ';';
case STMT_EXPR:
return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ';';
case STMT_SWITCH:
@@ -402,20 +495,22 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
case STMT_VAR:
switch (rng(3)) {
case 0:
unique_vars.push('c');
var name = createVarName(MANDATORY);
if (name === 'c') name = 'a';
unique_vars.pop();
return 'var ' + name + ';';
case 1:
// initializer can only have one expression
unique_vars.push('c');
var name = createVarName(MANDATORY);
if (name === 'c') name = 'b';
unique_vars.pop();
return 'var ' + name + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
default:
// initializer can only have one expression
unique_vars.push('c');
var n1 = createVarName(MANDATORY);
if (n1 === 'c') n1 = 'b';
var n2 = createVarName(MANDATORY);
if (n2 === 'c') n2 = 'b';
unique_vars.pop();
return 'var ' + n1 + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ', ' + n2 + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
}
case STMT_RETURN_ETC:
@@ -424,8 +519,8 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
case 1:
case 2:
case 3:
if (canBreak && rng(5) === 0) return 'break;';
if (canContinue && rng(5) === 0) return 'continue;';
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 (rng(3) == 0) return '/*3*/return;';
return 'return ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
@@ -456,8 +551,11 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
var nameLenBefore = VAR_NAMES.length;
var catchName = createVarName(MANDATORY);
var freshCatchName = VAR_NAMES.length !== nameLenBefore;
if (!catch_redef) unique_vars.push(catchName);
s += ' catch (' + catchName + ') { ' + createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + ' }';
if (freshCatchName) VAR_NAMES.splice(nameLenBefore, 1); // remove catch name
// remove catch name
if (!catch_redef) unique_vars.pop();
if (freshCatchName) VAR_NAMES.splice(nameLenBefore, 1);
}
if (n !== 0) s += ' finally { ' + createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + ' }';
return s;
@@ -470,25 +568,27 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
function createSwitchParts(recurmax, n, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) {
var hadDefault = false;
var s = '';
var s = [''];
canBreak = enableLoopControl(canBreak, CAN_BREAK);
while (n-- > 0) {
//hadDefault = n > 0; // disables weird `default` clause positioning (use when handling destabilizes)
if (hadDefault || rng(5) > 0) {
s += '' +
'case ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ':\n' +
createStatements(rng(3) + 1, recurmax, canThrow, CAN_BREAK, canContinue, cannotReturn, stmtDepth) +
'\n' +
(rng(10) > 0 ? ' break;' : '/* fall-through */') +
'\n';
s.push(
'case ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ':',
createStatements(rng(3) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
rng(10) > 0 ? ' break;' : '/* fall-through */',
''
);
} else {
hadDefault = true;
s += '' +
'default:\n' +
createStatements(rng(3) + 1, recurmax, canThrow, CAN_BREAK, canContinue, cannotReturn, stmtDepth) +
'\n';
s.push(
'default:',
createStatements(rng(3) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
''
);
}
}
return s;
return s.join('\n');
}
function createExpression(recurmax, noComma, stmtDepth, canThrow) {
@@ -530,37 +630,67 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow);
case p++:
return createExpression(recurmax, noComma, stmtDepth, canThrow) + '?' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ':' + createExpression(recurmax, noComma, stmtDepth, canThrow);
case p++:
case p++:
var nameLenBefore = VAR_NAMES.length;
unique_vars.push('c');
var name = createVarName(MAYBE); // note: this name is only accessible from _within_ the function. and immutable at that.
if (name === 'c') name = 'a';
var s = '';
switch(rng(4)) {
unique_vars.pop();
var s = [];
switch (rng(5)) {
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;
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;
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;
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(true) + 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;
}
VAR_NAMES.length = nameLenBefore;
return s;
return filterDirective(s).join('\n');
case p++:
case p++:
return createTypeofExpr(recurmax, stmtDepth, canThrow);
case p++:
return [
'new function() {',
rng(2) ? '' : createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ';',
'return ' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ';',
'}'
].join('\n');
case p++:
case p++:
// more like a parser test but perhaps comment nodes mess up the analysis?
@@ -600,19 +730,19 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
") || " + rng(10) + ").toString()[" +
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "] ";
case p++:
return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow);
return createArrayLiteral(recurmax, stmtDepth, canThrow);
case p++:
return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow);
return createObjectLiteral(recurmax, stmtDepth, canThrow);
case p++:
return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '[' +
return createArrayLiteral(recurmax, stmtDepth, canThrow) + '[' +
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']';
case p++:
return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '[' +
return createObjectLiteral(recurmax, stmtDepth, canThrow) + '[' +
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']';
case p++:
return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '.' + getDotKey();
return createArrayLiteral(recurmax, stmtDepth, canThrow) + '.' + getDotKey();
case p++:
return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '.' + getDotKey();
return createObjectLiteral(recurmax, stmtDepth, canThrow) + '.' + getDotKey();
case p++:
var name = getVarName();
return name + ' && ' + name + '[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']';
@@ -624,7 +754,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
return _createExpression(recurmax, noComma, stmtDepth, canThrow);
}
function createArrayLiteral(recurmax, noComma, stmtDepth, canThrow) {
function createArrayLiteral(recurmax, stmtDepth, canThrow) {
recurmax--;
var arr = "[";
for (var i = rng(6); --i >= 0;) {
@@ -657,18 +787,56 @@ var KEYS = [
"3",
].concat(SAFE_KEYS);
function getDotKey() {
return SAFE_KEYS[rng(SAFE_KEYS.length)];
function getDotKey(assign) {
var key;
do {
key = SAFE_KEYS[rng(SAFE_KEYS.length)];
} while (assign && key == "length");
return key;
}
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) + "), ";
function createAccessor(recurmax, stmtDepth, canThrow) {
var namesLenBefore = VAR_NAMES.length;
var s;
var prop1 = getDotKey();
if (rng(2) == 0) {
s = [
'get ' + prop1 + '(){',
strictMode(),
createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
createStatement(recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth, STMT_RETURN_ETC),
'},'
];
} else {
var prop2;
do {
prop2 = getDotKey();
} while (prop1 == prop2);
s = [
'set ' + prop1 + '(' + createVarName(MANDATORY) + '){',
strictMode(),
createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
'this.' + prop2 + createAssignment() + _createBinaryExpr(recurmax, COMMA_OK, stmtDepth, canThrow) + ';',
'},'
];
}
return obj + "})";
VAR_NAMES.length = namesLenBefore;
return filterDirective(s).join('\n');
}
function createObjectLiteral(recurmax, stmtDepth, canThrow) {
recurmax--;
var obj = ['({'];
for (var i = rng(6); --i >= 0;) {
if (rng(20) == 0) {
obj.push(createAccessor(recurmax, stmtDepth, canThrow));
} else {
var key = KEYS[rng(KEYS.length)];
obj.push(key + ':(' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + '),');
}
}
obj.push('})');
return obj.join('\n');
}
function createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
@@ -682,22 +850,23 @@ function _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
// intentionally generate more hardcore ops
if (--recurmax < 0) return createValue();
var assignee, expr;
switch (rng(30)) {
case 0:
return '(c = c + 1, ' + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
case 1:
return '(' + createUnarySafePrefix() + '(' + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + '))';
case 2:
var assignee = getVarName();
assignee = getVarName();
return '(' + assignee + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
case 3:
var assignee = getVarName();
var expr = '(' + assignee + '[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow)
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:
var assignee = getVarName();
var expr = '(' + assignee + '.' + getDotKey() + createAssignment()
assignee = getVarName();
expr = '(' + assignee + '.' + getDotKey(true) + createAssignment()
+ _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
return canThrow && rng(10) == 0 ? expr : '(' + assignee + ' && ' + expr + ')';
default:
@@ -754,17 +923,24 @@ function getVarName() {
function createVarName(maybe, dontStore) {
if (!maybe || rng(2)) {
var name = VAR_NAMES[rng(VAR_NAMES.length)];
var suffix = rng(3);
if (suffix) {
name += '_' + suffix;
if (!dontStore) VAR_NAMES.push(name);
}
var name;
do {
name = VAR_NAMES[rng(VAR_NAMES.length)];
if (suffix) name += '_' + suffix;
} while (unique_vars.indexOf(name) >= 0);
if (suffix && !dontStore) VAR_NAMES.push(name);
return name;
}
return '';
}
if (require.main !== module) {
exports.createTopLevelCode = createTopLevelCode;
exports.num_iterations = num_iterations;
return;
}
function try_beautify(code, result) {
try {
var beautified = UglifyJS.minify(code, {
@@ -802,8 +978,8 @@ var default_options = {
mangle: {
"cache": null,
"eval": false,
"ie8": false,
"keep_fnames": false,
"screw_ie8": true,
"toplevel": false,
},
output: infer_options(UglifyJS.OutputStream),
@@ -858,17 +1034,27 @@ function log(options) {
} else {
console.log("// !!! uglify failed !!!");
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):");
options = JSON.parse(options);
console.log(options);
console.log();
if (!ok) {
if (!ok && typeof uglify_code == "string") {
Object.keys(default_options).forEach(log_suspects.bind(null, options));
console.log("!!!!!! Failed... round", round);
}
}
var fallback_options = [ JSON.stringify({
compress: false,
mangle: false
}) ];
var minify_options = require("./ufuzz.json").map(function(options) {
options.fromString = true;
return JSON.stringify(options);
@@ -878,17 +1064,9 @@ var uglify_code, uglify_result, ok;
for (var round = 1; round <= num_iterations; round++) {
process.stdout.write(round + " of " + num_iterations + "\r");
VAR_NAMES.length = INITIAL_NAMES_LEN; // prune any previous names still in the list
loops = 0;
funcs = 0;
original_code = [
"var a = 100, b = 10, c = 0;",
createTopLevelCode(),
"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) {
original_code = createTopLevelCode();
original_result = sandbox.run_code(original_code);
(typeof original_result != "string" ? fallback_options : minify_options).forEach(function(options) {
try {
uglify_code = UglifyJS.minify(original_code, JSON.parse(options)).code;
} catch (e) {
@@ -897,12 +1075,13 @@ for (var round = 1; round <= num_iterations; round++) {
ok = typeof uglify_code == "string";
if (ok) {
original_result = sandbox.run_code(original_code);
uglify_result = sandbox.run_code(uglify_code);
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);
else if (verbose_error && typeof original_result != "string") {
else if (typeof original_result != "string") {
console.log("//=============================================================");
console.log("// original code");
try_beautify(original_code, original_result);