Compare commits

...

34 Commits

Author SHA1 Message Date
Alex Lam S.L
4eb4cb656c v3.3.9 2018-01-27 12:56:34 +00:00
Alex Lam S.L
193612ac67 fix accounting after conversion to assignment (#2847)
Missing reference to `AST_SymbolRef` created by `unused` causes `collapse_vars` to misbehave.

fixes #2846
2018-01-26 14:21:11 +08:00
Alex Lam S.L
95cfce68ea backport of #2835 (#2841) 2018-01-23 05:45:45 +08:00
Alex Lam S.L
ec4202590d drop assignments to constant expressions only (#2839)
fixes #2838
2018-01-23 02:49:54 +08:00
Alex Lam S.L
5e2cd07d6f handle duplicate function declarations correctly (#2837)
fixes #2836
2018-01-23 01:28:09 +08:00
Alex Lam S.L
06166df999 v3.3.8 2018-01-21 07:08:01 +00:00
Alex Lam S.L
e2dc9cf091 fix unsafe evaluate of AST_Array (#2825)
fixes #2822
2018-01-21 01:39:44 +08:00
Alex Lam S.L
069df27bf1 enable unsafe for test/ufuzz.js (#2819)
- introduce `unsafe_undefined`
- safer `.toString()` compression

Miscellaneous
- rename `unsafe_Function`
2018-01-19 23:47:42 +08:00
Alex Lam S.L
3e7873217c improve unused on built-in functions (#2817) 2018-01-19 20:41:57 +08:00
Alex Lam S.L
e21bab7ce6 avoid duplicate property names in object literals under "use strict" (#2818)
fixes #2816
2018-01-19 20:13:50 +08:00
Alex Lam S.L
ac9a168fba fix & improve test/ufuzz.js (#2815)
- use correct `options` when testing `rename`
- mask arbitrarily assigned function IDs to reduce rate of false positives
2018-01-19 16:51:59 +08:00
Alex Lam S.L
81b64549ce fix time-out for respawned test/ufuzz.js (#2814) 2018-01-19 06:11:19 +08:00
Alex Lam S.L
082e004b87 compress undefined property names (#2811)
- enforce property names as string
- handle `void 0` as `undefined` in `hoist_props` & `reduce_vars`
2018-01-19 00:36:30 +08:00
kzc
983e69128b fix join_vars property assignment for negative array index (#2810)
fixes #2790
2018-01-18 21:52:54 +08:00
Alex Lam S.L
b335912e86 enhance test/ufuzz.js (#2808)
- standalone test for `rename`
- handle `keep_fargs` & `rename` upon failure
2018-01-18 14:08:05 +08:00
Alex Lam S.L
cc07f3b806 faster output of comments (#2806) 2018-01-18 02:57:33 +08:00
Alex Lam S.L
07e4b64f3a fix AST_Scope.clone() (#2803)
fixes #2799
2018-01-17 21:33:13 +08:00
Alex Lam S.L
d3ce2bc9e7 suppress unsafe_proto for LHS expressions (#2804) 2018-01-17 20:41:51 +08:00
Alex Lam S.L
cff3bf4914 configure rename with CLI (#2802) 2018-01-17 15:12:22 +08:00
Alex Lam S.L
79cfac77bd extend join_vars & sequences (#2798) 2018-01-17 13:58:27 +08:00
Alex Lam S.L
224c14d49d improve mocha tests (#2797)
- workaround sporadic delays from Travis CI
2018-01-16 17:51:25 +08:00
Alex Lam S.L
7857354d85 improve test/travis-ufuzz.js (#2795)
- print usage
- support concurrent jobs
- improve instance utilisation
- resume after V8 self-destruct
2018-01-16 17:33:21 +08:00
Alex Lam S.L
b4aef753e7 general improvements around AST_ForIn (#2796)
- compress using `collapse_vars`
- remove unused `name`
- simplify `loop_body`
2018-01-16 17:03:12 +08:00
Joël Galeran
424173d311 fix typo in README (#2792) 2018-01-16 10:29:38 +08:00
Alex Lam S.L
ec7cd1dcf7 handle VM failure gracefully (#2791) 2018-01-15 23:41:39 +08:00
Alex Lam S.L
7def684730 improve test/travis-ufuzz.js (#2789)
- wait for instance to boot
- run on forked repositories
- workaround `request_limit_reached`
2018-01-15 19:18:21 +08:00
Alex Lam S.L
10f961c27b enhance collapse_vars (#2788) 2018-01-15 18:47:23 +08:00
Alex Lam S.L
b483678ca7 avoid suboptimal termination in passes (#2787) 2018-01-15 16:42:31 +08:00
Alex Lam S.L
cbbe6fad60 avoid double counting within single-use functions (#2785)
fixes #2783
2018-01-15 16:42:15 +08:00
Alex Lam S.L
f96929c031 improve test/travis-ufuzz.js (#2786)
- use more RAM
- show progress in console
- report failure as job status
2018-01-15 15:08:35 +08:00
Alex Lam S.L
2b6657e967 run test/ufuzz.js when Travis CI is idle (#2784) 2018-01-15 08:52:11 +08:00
Alex Lam S.L
7c0c92943f v3.3.7 2018-01-14 09:13:26 +00:00
Alex Lam S.L
62a66dfff4 fix & extend join_vars for object assigments (#2781) 2018-01-14 17:11:31 +08:00
kzc
2cab348341 improve SymbolDef info in --output ast (#2778)
* SymbolDef info (a.k.a. `thedef`) is now represented as a string containing `"ID name [mangled_name]"`. 
* Enhance display of `globals`, `variables`, `functions` and `enclosed`.
* `SymbolDef.next_id` starts at `1` and the `id` is adjusted for `-o ast` display.
2018-01-14 01:40:51 +08:00
29 changed files with 1290 additions and 227 deletions

View File

@@ -70,7 +70,7 @@ a double dash to prevent input files being used as option arguments:
`debug` Add debug prefix and suffix.
`domprops` Mangle property names that overlaps
with DOM properties.
`keep_quoted` Only mangle unquoted properies.
`keep_quoted` Only mangle unquoted properties.
`regex` Only mangle matched property names.
`reserved` List of names that should not be mangled.
-b, --beautify [options] Beautify output/specify output options:
@@ -737,7 +737,7 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u
comparison are switching. Compression only works if both `comparisons` and
`unsafe_comps` are both set to true.
- `unsafe_Func` (default: `false`) -- compress and mangle `Function(args, code)`
- `unsafe_Function` (default: `false`) -- compress and mangle `Function(args, code)`
when both `args` and `code` are string literals.
- `unsafe_math` (default: `false`) -- optimize numerical expressions like
@@ -749,6 +749,10 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u
- `unsafe_regexp` (default: `false`) -- enable substitutions of variables with
`RegExp` values the same way as if they are constants.
- `unsafe_undefined` (default: `false`) -- substitute `void 0` if there is a
variable named `undefined` in scope (variable name will be mangled, typically
reduced to a single character)
- `unused` (default: `true`) -- drop unreferenced functions and variables (simple
direct variable assignments do not count as references unless set to `"keep_assign"`)
@@ -922,9 +926,6 @@ when this flag is on:
- `new Object()``{}`
- `String(exp)` or `exp.toString()``"" + exp`
- `new Object/RegExp/Function/Error/Array (...)` → we discard the `new`
- `void 0``undefined` (if there is a variable named "undefined" in
scope; we do it because the variable name will be mangled, typically
reduced to a single character)
### Conditional compilation

View File

@@ -43,6 +43,7 @@ program.option("-d, --define <expr>[=value]", "Global definitions.", parse_js("d
program.option("--ie8", "Support non-standard Internet Explorer 8.");
program.option("--keep-fnames", "Do not mangle/drop function names. Useful for code relying on Function.prototype.name.");
program.option("--name-cache <file>", "File to hold mangled name mappings.");
program.option("--rename", "Force symbol expansion.");
program.option("--no-rename", "Disable symbol expansion.");
program.option("--self", "Build UglifyJS as a library (implies --wrap UglifyJS)");
program.option("--source-map [options]", "Enable source map/specify source map options.", parse_source_map());
@@ -62,13 +63,11 @@ if (!program.output && program.sourceMap && program.sourceMap.url != "inline") {
"compress",
"ie8",
"mangle",
"rename",
"sourceMap",
"toplevel",
"wrap"
].forEach(function(name) {
if (name in program) {
if (name == "rename" && program[name]) return;
options[name] = program[name];
}
});
@@ -121,6 +120,11 @@ if (program.parse) {
fatal("ERROR: inline source map only works with built-in parser");
}
}
if (~program.rawArgs.indexOf("--rename")) {
options.rename = true;
} else if (!program.rename) {
options.rename = false;
}
var convert_path = function(name) {
return name;
};
@@ -227,28 +231,15 @@ function run() {
result.ast.figure_out_scope({});
}
print(JSON.stringify(result.ast, function(key, value) {
switch (key) {
if (value) switch (key) {
case "thedef":
if (typeof value == "object" && typeof value.id == "number") {
return value.id;
}
return;
return symdef(value);
case "enclosed":
return value.map(function(sym){
return sym.id;
});
return value.length ? value.map(symdef) : undefined;
case "variables":
case "functions":
case "globals":
if (value && value.size()) {
var ret = {};
value.each(function(val, key) {
// key/val inverted for readability.
ret[val.id] = key;
});
return ret;
}
return;
return value.size() ? value.map(symdef) : undefined;
}
if (skip_key(key)) return;
if (value instanceof UglifyJS.AST_Token) return;
@@ -403,6 +394,12 @@ function skip_key(key) {
return skip_keys.indexOf(key) >= 0;
}
function symdef(def) {
var ret = (1e6 + def.id) + " " + def.name;
if (def.mangled_name) ret += " " + def.mangled_name;
return ret;
}
function format_object(obj) {
var lines = [];
var padding = "";

View File

@@ -267,11 +267,10 @@ var AST_For = DEFNODE("For", "init condition step", {
}
}, AST_IterationStatement);
var AST_ForIn = DEFNODE("ForIn", "init name object", {
var AST_ForIn = DEFNODE("ForIn", "init object", {
$documentation: "A `for ... in` statement",
$propdoc: {
init: "[AST_Node] the `for/in` initialization code",
name: "[AST_SymbolRef?] the loop variable, only if `init` is AST_Var",
object: "[AST_Node] the object that we're looping through"
},
_walk: function(visitor) {
@@ -309,6 +308,13 @@ var AST_Scope = DEFNODE("Scope", "variables functions uses_with uses_eval parent
enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes",
cname: "[integer/S] current index for mangling variables (used internally by the mangler)",
},
clone: function(deep) {
var node = this._clone(deep);
if (this.variables) node.variables = this.variables.clone();
if (this.functions) node.functions = this.functions.clone();
if (this.enclosed) node.enclosed = this.enclosed.slice();
return node;
}
}, AST_Block);
var AST_Toplevel = DEFNODE("Toplevel", "globals", {
@@ -683,8 +689,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 AST_SymbolAccessor.",
value: "[AST_Node] property value. For setters and getters this is an AST_Accessor."
key: "[string|AST_SymbolAccessor] property name. For ObjectKeyVal this is a string. For getters and setters this is an AST_SymbolAccessor.",
value: "[AST_Node] property value. For getters and setters this is an AST_Accessor."
},
_walk: function(visitor) {
return visitor._visit(this, function(){

View File

@@ -84,10 +84,11 @@ function Compressor(options, false_by_default) {
typeofs : !false_by_default,
unsafe : false,
unsafe_comps : false,
unsafe_Func : false,
unsafe_Function: false,
unsafe_math : false,
unsafe_proto : false,
unsafe_regexp : false,
unsafe_undefined: false,
unused : !false_by_default,
warnings : false,
}, true);
@@ -171,7 +172,8 @@ merge(Compressor.prototype, {
node.process_expression(true);
}
var passes = +this.options.passes || 1;
var last_count = 1 / 0;
var min_count = 1 / 0;
var stopping = false;
var mangle = { ie8: this.option("ie8") };
for (var pass = 0; pass < passes; pass++) {
node.figure_out_scope(mangle);
@@ -183,9 +185,15 @@ merge(Compressor.prototype, {
node.walk(new TreeWalker(function() {
count++;
}));
this.info("pass " + pass + ": last_count: " + last_count + ", count: " + count);
if (count >= last_count) break;
last_count = count;
this.info("pass " + pass + ": last_count: " + min_count + ", count: " + count);
if (count < min_count) {
min_count = count;
stopping = false;
} else if (stopping) {
break;
} else {
stopping = true;
}
}
}
if (this.option("expression")) {
@@ -397,14 +405,15 @@ merge(Compressor.prototype, {
}
function read_property(obj, key) {
if (key instanceof AST_Constant) key = key.getValue();
if (key instanceof AST_Node) return null;
key = get_value(key);
if (key instanceof AST_Node) return;
var value;
if (obj instanceof AST_Array) {
var elements = obj.elements;
if (key == "length") return make_node_from_constant(elements.length, obj);
if (typeof key == "number" && key in elements) value = elements[key];
} else if (obj instanceof AST_Object) {
key = "" + key;
var props = obj.properties;
for (var i = props.length; --i >= 0;) {
var prop = props[i];
@@ -840,9 +849,8 @@ merge(Compressor.prototype, {
};
function loop_body(x) {
if (x instanceof AST_Switch) return x;
if (x instanceof AST_For || x instanceof AST_ForIn || x instanceof AST_DWLoop) {
return (x.body instanceof AST_BlockStatement ? x.body : x);
if (x instanceof AST_IterationStatement) {
return x.body instanceof AST_BlockStatement ? x.body : x;
}
return x;
};
@@ -886,7 +894,7 @@ merge(Compressor.prototype, {
sequencesize_2(statements, compressor);
}
if (compressor.option("join_vars")) {
join_consecutive_vars(statements, compressor);
join_consecutive_vars(statements);
}
if (compressor.option("collapse_vars")) {
collapse(statements, compressor);
@@ -1174,14 +1182,31 @@ merge(Compressor.prototype, {
expr.definitions.forEach(extract_candidates);
} else if (expr instanceof AST_DWLoop) {
extract_candidates(expr.condition);
if (!(expr.body instanceof AST_Block)) {
extract_candidates(expr.body);
}
} else if (expr instanceof AST_Exit) {
if (expr.value) extract_candidates(expr.value);
} else if (expr instanceof AST_For) {
if (expr.init) extract_candidates(expr.init);
if (expr.condition) extract_candidates(expr.condition);
if (expr.step) extract_candidates(expr.step);
if (!(expr.body instanceof AST_Block)) {
extract_candidates(expr.body);
}
} else if (expr instanceof AST_ForIn) {
extract_candidates(expr.object);
if (!(expr.body instanceof AST_Block)) {
extract_candidates(expr.body);
}
} else if (expr instanceof AST_If) {
extract_candidates(expr.condition);
if (!(expr.body instanceof AST_Block)) {
extract_candidates(expr.body);
}
if (expr.alternative && !(expr.alternative instanceof AST_Block)) {
extract_candidates(expr.alternative);
}
} else if (expr instanceof AST_Sequence) {
expr.expressions.forEach(extract_candidates);
} else if (expr instanceof AST_SimpleStatement) {
@@ -1208,10 +1233,12 @@ merge(Compressor.prototype, {
if (parent instanceof AST_Call) return node;
if (parent instanceof AST_Case) return node;
if (parent instanceof AST_Conditional) return node;
if (parent instanceof AST_Definitions) return find_stop(parent, level + 1);
if (parent instanceof AST_Exit) return node;
if (parent instanceof AST_If) return node;
if (parent instanceof AST_IterationStatement) return node;
if (parent instanceof AST_Sequence) return find_stop(parent, level + 1);
if (parent instanceof AST_SimpleStatement) return find_stop(parent, level + 1);
if (parent instanceof AST_Switch) return node;
if (parent instanceof AST_VarDef) return node;
return null;
@@ -1615,7 +1642,7 @@ merge(Compressor.prototype, {
var stat = null;
for (var i = 0, len = block.body.length; i < len; i++) {
var line = block.body[i];
if (line instanceof AST_Definitions && declarations_only(line)) {
if (line instanceof AST_Var && declarations_only(line)) {
decls.push(line);
} else if (stat) {
return false;
@@ -1637,37 +1664,34 @@ merge(Compressor.prototype, {
for (var i = 0; i < statements.length; i++) {
var stat = statements[i];
if (prev) {
if (stat instanceof AST_For && !(stat.init instanceof AST_Definitions)) {
var abort = false;
prev.body.walk(new TreeWalker(function(node) {
if (abort || node instanceof AST_Scope) return true;
if (node instanceof AST_Binary && node.operator == "in") {
abort = true;
return true;
}
}));
if (!abort) {
if (stat.init) stat.init = cons_seq(stat.init);
else {
stat.init = prev.body;
n--;
CHANGED = true;
if (stat instanceof AST_Exit) {
stat.value = cons_seq(stat.value || make_node(AST_Undefined, stat).transform(compressor));
} else if (stat instanceof AST_For) {
if (!(stat.init instanceof AST_Definitions)) {
var abort = false;
prev.body.walk(new TreeWalker(function(node) {
if (abort || node instanceof AST_Scope) return true;
if (node instanceof AST_Binary && node.operator == "in") {
abort = true;
return true;
}
}));
if (!abort) {
if (stat.init) stat.init = cons_seq(stat.init);
else {
stat.init = prev.body;
n--;
CHANGED = true;
}
}
}
}
else if (stat instanceof AST_If) {
} else if (stat instanceof AST_ForIn) {
stat.object = cons_seq(stat.object);
} else if (stat instanceof AST_If) {
stat.condition = cons_seq(stat.condition);
}
else if (stat instanceof AST_With) {
} else if (stat instanceof AST_Switch) {
stat.expression = cons_seq(stat.expression);
}
else if (stat instanceof AST_Exit && stat.value) {
stat.value = cons_seq(stat.value);
}
else if (stat instanceof AST_Exit) {
stat.value = cons_seq(make_node(AST_Undefined, stat).transform(compressor));
}
else if (stat instanceof AST_Switch) {
} else if (stat instanceof AST_With) {
stat.expression = cons_seq(stat.expression);
}
}
@@ -1712,6 +1736,7 @@ merge(Compressor.prototype, {
do {
var node = exprs[0];
if (!(node instanceof AST_Assign)) break;
if (node.operator != "=") break;
if (!(node.left instanceof AST_PropAccess)) break;
var sym = node.left.expression;
if (!(sym instanceof AST_SymbolRef)) break;
@@ -1722,6 +1747,12 @@ merge(Compressor.prototype, {
prop = prop.evaluate(compressor);
}
if (prop instanceof AST_Node) break;
prop = "" + prop;
if (compressor.has_directive("use strict")) {
if (!all(def.value.properties, function(node) {
return node.key != prop && node.key.name != prop;
})) break;
}
def.value.properties.push(make_node(AST_ObjectKeyVal, node, {
key: prop,
value: node.right
@@ -1732,7 +1763,7 @@ merge(Compressor.prototype, {
return trimmed && exprs;
}
function join_consecutive_vars(statements, compressor) {
function join_consecutive_vars(statements) {
var defs;
for (var i = 0, j = -1, len = statements.length; i < len; i++) {
var stat = statements[i];
@@ -1748,8 +1779,15 @@ merge(Compressor.prototype, {
statements[++j] = stat;
defs = stat;
}
} else if (stat instanceof AST_Exit) {
stat.value = extract_object_assignments(stat.value);
} else if (stat instanceof AST_For) {
if (prev instanceof AST_Var && (!stat.init || stat.init.TYPE == prev.TYPE)) {
var exprs = join_object_assignments(prev, stat.init);
if (exprs) {
CHANGED = true;
stat.init = exprs.length ? make_sequence(stat.init, exprs) : null;
statements[++j] = stat;
} else if (prev instanceof AST_Var && (!stat.init || stat.init.TYPE == prev.TYPE)) {
if (stat.init) {
prev.definitions = prev.definitions.concat(stat.init.definitions);
}
@@ -1764,6 +1802,10 @@ merge(Compressor.prototype, {
} else {
statements[++j] = stat;
}
} else if (stat instanceof AST_ForIn) {
stat.object = extract_object_assignments(stat.object);
} else if (stat instanceof AST_If) {
stat.condition = extract_object_assignments(stat.condition);
} else if (stat instanceof AST_SimpleStatement) {
var exprs = join_object_assignments(prev, stat.body);
if (exprs) {
@@ -1772,11 +1814,31 @@ merge(Compressor.prototype, {
stat.body = make_sequence(stat.body, exprs);
}
statements[++j] = stat;
} else if (stat instanceof AST_Switch) {
stat.expression = extract_object_assignments(stat.expression);
} else if (stat instanceof AST_With) {
stat.expression = extract_object_assignments(stat.expression);
} else {
statements[++j] = stat;
}
}
statements.length = j + 1;
function extract_object_assignments(value) {
statements[++j] = stat;
var exprs = join_object_assignments(prev, value);
if (exprs) {
CHANGED = true;
if (exprs.length) {
return make_sequence(value, exprs);
} else if (value instanceof AST_Sequence) {
return value.tail_node().left;
} else {
return value.left;
}
}
return value;
}
}
}
@@ -1801,6 +1863,18 @@ merge(Compressor.prototype, {
}));
};
function get_value(key) {
if (key instanceof AST_Constant) {
return key.getValue();
}
if (key instanceof AST_UnaryPrefix
&& key.operator == "void"
&& key.expression instanceof AST_Constant) {
return;
}
return key;
}
function is_undefined(node, compressor) {
return node.is_undefined
|| node instanceof AST_Undefined
@@ -2040,6 +2114,95 @@ merge(Compressor.prototype, {
return (first_in_statement(compressor) ? best_of_statement : best_of_expression)(ast1, ast2);
}
function convert_to_predicate(obj) {
for (var key in obj) {
obj[key] = makePredicate(obj[key]);
}
}
var object_fns = [
"constructor",
"toString",
"valueOf",
];
var native_fns = {
Array: [
"indexOf",
"join",
"lastIndexOf",
"slice",
].concat(object_fns),
Boolean: object_fns,
Number: [
"toExponential",
"toFixed",
"toPrecision",
].concat(object_fns),
Object: object_fns,
RegExp: [
"test",
].concat(object_fns),
String: [
"charAt",
"charCodeAt",
"concat",
"indexOf",
"italics",
"lastIndexOf",
"match",
"replace",
"search",
"slice",
"split",
"substr",
"substring",
"trim",
].concat(object_fns),
};
convert_to_predicate(native_fns);
var static_fns = {
Array: [
"isArray",
],
Math: [
"abs",
"acos",
"asin",
"atan",
"ceil",
"cos",
"exp",
"floor",
"log",
"round",
"sin",
"sqrt",
"tan",
"atan2",
"pow",
"max",
"min",
],
Number: [
"isFinite",
"isNaN",
],
Object: [
"create",
"getOwnPropertyDescriptor",
"getOwnPropertyNames",
"getPrototypeOf",
"isExtensible",
"isFrozen",
"isSealed",
"keys",
],
String: [
"fromCharCode",
],
};
convert_to_predicate(static_fns);
// methods to evaluate a constant expression
(function(def){
// If the node has been successfully reduced to a constant,
@@ -2077,7 +2240,10 @@ merge(Compressor.prototype, {
var elements = [];
for (var i = 0, len = this.elements.length; i < len; i++) {
var element = this.elements[i];
if (element instanceof AST_Function) continue;
if (element instanceof AST_Function) {
elements.push(element);
continue;
}
var value = element._eval(compressor, depth);
if (element === value) return this;
elements.push(value);
@@ -2205,13 +2371,9 @@ merge(Compressor.prototype, {
Array: Array,
Math: Math,
Number: Number,
Object: Object,
String: String,
};
function convert_to_predicate(obj) {
for (var key in obj) {
obj[key] = makePredicate(obj[key]);
}
}
var static_values = {
Math: [
"E",
@@ -2252,77 +2414,6 @@ merge(Compressor.prototype, {
}
return this;
});
var object_fns = [
"constructor",
"toString",
"valueOf",
];
var native_fns = {
Array: [
"indexOf",
"join",
"lastIndexOf",
"slice",
].concat(object_fns),
Boolean: object_fns,
Number: [
"toExponential",
"toFixed",
"toPrecision",
].concat(object_fns),
RegExp: [
"test",
].concat(object_fns),
String: [
"charAt",
"charCodeAt",
"concat",
"indexOf",
"italics",
"lastIndexOf",
"match",
"replace",
"search",
"slice",
"split",
"substr",
"substring",
"trim",
].concat(object_fns),
};
convert_to_predicate(native_fns);
var static_fns = {
Array: [
"isArray",
],
Math: [
"abs",
"acos",
"asin",
"atan",
"ceil",
"cos",
"exp",
"floor",
"log",
"round",
"sin",
"sqrt",
"tan",
"atan2",
"pow",
"max",
"min"
],
Number: [
"isFinite",
"isNaN",
],
String: [
"fromCharCode",
],
};
convert_to_predicate(static_fns);
def(AST_Call, function(compressor, depth) {
var exp = this.expression;
if (compressor.option("unsafe") && exp instanceof AST_PropAccess) {
@@ -2347,7 +2438,16 @@ merge(Compressor.prototype, {
if (arg === value) return this;
args.push(value);
}
return val[key].apply(val, args);
try {
return val[key].apply(val, args);
} catch (ex) {
compressor.warn("Error evaluating {code} [{file}:{line},{col}]", {
code: this.print_to_string(),
file: this.start.file,
line: this.start.line,
col: this.start.col
});
}
}
return this;
});
@@ -2438,9 +2538,34 @@ merge(Compressor.prototype, {
if (compressor.option("unsafe")) {
var expr = this.expression;
if (is_undeclared_ref(expr) && global_pure_fns(expr.name)) return true;
if (expr instanceof AST_Dot
&& is_undeclared_ref(expr.expression)
&& (static_fns[expr.expression.name] || return_false)(expr.property)) {
return true;
}
}
return this.pure || !compressor.pure_funcs(this);
});
AST_Node.DEFMETHOD("is_call_pure", return_false);
AST_Dot.DEFMETHOD("is_call_pure", function(compressor) {
if (!compressor.option("unsafe")) return;
var expr = this.expression;
var fns = return_false;
if (expr instanceof AST_Array) {
fns = native_fns.Array;
} else if (expr.is_boolean()) {
fns = native_fns.Boolean;
} else if (expr.is_number(compressor)) {
fns = native_fns.Number;
} else if (expr instanceof AST_RegExp) {
fns = native_fns.RegExp;
} else if (expr.is_string(compressor)) {
fns = native_fns.String;
} else if (!this.may_throw_on_access(compressor)) {
fns = native_fns.Object;
}
return fns(this.property);
});
// determine if expression has side effects
(function(def){
@@ -2461,8 +2586,12 @@ merge(Compressor.prototype, {
return any(this.body, compressor);
});
def(AST_Call, function(compressor){
return !this.is_expr_pure(compressor)
|| any(this.args, compressor);
if (!this.is_expr_pure(compressor)
&& (!this.expression.is_call_pure(compressor)
|| this.expression.has_side_effects(compressor))) {
return true;
}
return any(this.args, compressor);
});
def(AST_Switch, function(compressor){
return this.expression.has_side_effects(compressor)
@@ -2909,9 +3038,11 @@ merge(Compressor.prototype, {
if (var_defs.length > 1 && (!def.value || sym.orig.indexOf(def.name) > sym.eliminated)) {
compressor.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name));
if (def.value) {
var ref = make_node(AST_SymbolRef, def.name, def.name);
sym.references.push(ref);
var assign = make_node(AST_Assign, def, {
operator: "=",
left: make_node(AST_SymbolRef, def.name, def.name),
left: ref,
right: def.value
});
if (fixed_ids[sym.id] === def) {
@@ -3241,9 +3372,7 @@ merge(Compressor.prototype, {
if (node instanceof AST_PropAccess && node.expression instanceof AST_SymbolRef) {
var defs = defs_by_id[node.expression.definition().id];
if (defs) {
var key = node.property;
if (key instanceof AST_Node) key = key.getValue();
var def = defs.get(key);
var def = defs.get(get_value(node.property));
var sym = make_node(AST_SymbolRef, node, {
name: def.name,
scope: node.expression.scope,
@@ -3294,6 +3423,12 @@ merge(Compressor.prototype, {
def(AST_This, return_null);
def(AST_Call, function(compressor, first_in_statement){
if (!this.is_expr_pure(compressor)) {
if (this.expression.is_call_pure(compressor)) {
var exprs = this.args.slice();
exprs.unshift(this.expression.expression);
exprs = trim(exprs, compressor, first_in_statement);
return exprs && make_sequence(this, exprs);
}
if (this.expression instanceof AST_Function
&& (!this.expression.name || !this.expression.name.definition().references.length)) {
var node = this.clone();
@@ -3336,8 +3471,10 @@ merge(Compressor.prototype, {
while (left instanceof AST_PropAccess) {
left = left.expression;
}
if (left instanceof AST_Symbol) return this;
return this.right.drop_side_effect_free(compressor);
if (left.is_constant_expression(compressor.find_parent(AST_Scope))) {
return this.right.drop_side_effect_free(compressor);
}
return this;
});
def(AST_Conditional, function(compressor){
var consequent = this.consequent.drop_side_effect_free(compressor);
@@ -3971,11 +4108,13 @@ merge(Compressor.prototype, {
break;
} else if (exp instanceof AST_Dot) switch(exp.property) {
case "toString":
if (self.args.length == 0) return make_node(AST_Binary, self, {
left: make_node(AST_String, self, { value: "" }),
operator: "+",
right: exp.expression
}).optimize(compressor);
if (self.args.length == 0 && !exp.expression.may_throw_on_access(compressor)) {
return make_node(AST_Binary, self, {
left: make_node(AST_String, self, { value: "" }),
operator: "+",
right: exp.expression
}).optimize(compressor);
}
break;
case "join":
if (exp.expression instanceof AST_Array) EXIT: {
@@ -4083,7 +4222,7 @@ merge(Compressor.prototype, {
break;
}
}
if (compressor.option("unsafe_Func")
if (compressor.option("unsafe_Function")
&& is_undeclared_ref(exp)
&& exp.name == "Function") {
// new Function() => function(){}
@@ -5003,6 +5142,7 @@ merge(Compressor.prototype, {
}
if (single_use && fixed) {
if (fixed instanceof AST_Defun) {
fixed._squeezed = true;
fixed = make_node(AST_Function, fixed, fixed);
}
var value;
@@ -5090,7 +5230,7 @@ merge(Compressor.prototype, {
}
OPT(AST_Undefined, function(self, compressor){
if (compressor.option("unsafe")) {
if (compressor.option("unsafe_undefined")) {
var undef = find_variable(compressor, "undefined");
if (undef) {
var ref = make_node(AST_SymbolRef, self, {
@@ -5589,6 +5729,7 @@ merge(Compressor.prototype, {
if (def) {
return def.optimize(compressor);
}
if (is_lhs(self, compressor.parent())) return self;
if (compressor.option("unsafe_proto")
&& self.expression instanceof AST_Dot
&& self.expression.property == "prototype") {
@@ -5627,7 +5768,6 @@ merge(Compressor.prototype, {
break;
}
}
if (is_lhs(self, compressor.parent())) return self;
var sub = self.flatten_object(self.property, compressor);
if (sub) return sub.optimize(compressor);
var ev = self.evaluate(compressor);

View File

@@ -451,6 +451,11 @@ function OutputStream(options) {
return OUTPUT;
};
function has_nlb() {
var index = OUTPUT.lastIndexOf("\n");
return /^ *$/.test(OUTPUT.slice(index + 1));
}
function prepend_comments(node) {
var self = this;
var start = node.start;
@@ -499,7 +504,7 @@ function OutputStream(options) {
comments = comments.filter(comment_filter, node);
if (comments.length == 0) return;
var last_nlb = /(^|\n) *$/.test(OUTPUT);
var last_nlb = has_nlb();
comments.forEach(function(c, i) {
if (!last_nlb) {
if (c.nlb) {
@@ -546,7 +551,7 @@ function OutputStream(options) {
print("\n");
indent();
need_newline_indented = false;
} else if (c.nlb && (i > 0 || !/(^|\n) *$/.test(OUTPUT))) {
} else if (c.nlb && (i > 0 || !has_nlb())) {
print("\n");
indent();
} else if (i > 0 || !tail) {
@@ -1344,11 +1349,8 @@ function OutputStream(options) {
function print_property_name(key, quote, output) {
if (output.option("quote_keys")) {
output.print_string(key + "");
} else if ((typeof key == "number"
|| !output.option("beautify")
&& +key + "" == key)
&& parseFloat(key) >= 0) {
output.print_string(key);
} else if ("" + +key == key && key >= 0) {
output.print(make_num(key));
} else if (RESERVED_WORDS(key) ? !output.option("ie8") : is_identifier_string(key)) {
if (quote && output.option("keep_quoted_props")) {

View File

@@ -1054,12 +1054,10 @@ function parse($TEXT, options) {
};
function for_in(init) {
var lhs = init instanceof AST_Var ? init.definitions[0].name : null;
var obj = expression(true);
expect(")");
return new AST_ForIn({
init : init,
name : lhs,
object : obj,
body : in_loop(statement)
});
@@ -1367,7 +1365,7 @@ function parse($TEXT, options) {
if (type == "name" && !is("punc", ":")) {
var key = new AST_SymbolAccessor({
start: S.token,
name: as_property_name(),
name: "" + as_property_name(),
end: prev()
});
if (name == "get") {
@@ -1393,7 +1391,7 @@ function parse($TEXT, options) {
a.push(new AST_ObjectKeyVal({
start : start,
quote : start.quote,
key : name,
key : "" + name,
value : expression(false),
end : prev()
}));

View File

@@ -57,7 +57,7 @@ function SymbolDef(scope, orig, init) {
this.id = SymbolDef.next_id++;
};
SymbolDef.next_id = 1e6;
SymbolDef.next_id = 1;
SymbolDef.prototype = {
unmangleable: function(options) {
@@ -305,7 +305,7 @@ AST_Scope.DEFMETHOD("find_variable", function(name){
AST_Scope.DEFMETHOD("def_function", function(symbol, init){
var def = this.def_variable(symbol, init);
if (!def.init) def.init = init;
if (!def.init || def.init instanceof AST_Defun) def.init = init;
this.functions.set(symbol.name, def);
return def;
});

View File

@@ -303,6 +303,13 @@ Dictionary.prototype = {
ret.push(f(this._values[i], i.substr(1)));
return ret;
},
clone: function() {
var ret = new Dictionary();
for (var i in this._values)
ret._values[i] = this._values[i];
ret._size = this._size;
return ret;
},
toObject: function() { return this._values }
};
Dictionary.fromObject = function(obj) {

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

View File

@@ -4012,3 +4012,103 @@ replace_all_var: {
}
expect_stdout: "PASS"
}
cascade_statement: {
options = {
collapse_vars: true,
}
input: {
function f1(a, b) {
var c;
if (a)
return c = b, c || a;
else
c = a, c(b);
}
function f2(a, b) {
var c;
while (a)
c = b, a = c + b;
do
throw c = a + b, c;
while (c);
}
function f3(a, b) {
for (; a < b; a++)
if (c = a, c && b)
var c = (c = b(a), c);
}
}
expect: {
function f1(a, b) {
var c;
if (a)
return (c = b) || a;
else
(c = a)(b);
}
function f2(a, b) {
var c;
while (a)
a = (c = b) + b;
do
throw c = a + b;
while (c);
}
function f3(a, b) {
for (; a < b; a++)
if ((c = a) && b)
var c = c = b(a);
}
}
}
cascade_forin: {
options = {
collapse_vars: true,
}
input: {
var a;
function f(b) {
return [ b, b, b ];
}
for (var c in a = console, f(a))
console.log(c);
}
expect: {
var a;
function f(b) {
return [ b, b, b ];
}
for (var c in f(a = console))
console.log(c);
}
expect_stdout: [
"0",
"1",
"2",
]
}
unsafe_builtin: {
options = {
collapse_vars: true,
pure_getters: "strict",
unsafe: true,
unused: true,
}
input: {
function f(a) {
var b = Math.abs(a);
return Math.pow(b, 2);
}
console.log(f(-1), f(2));
}
expect: {
function f(a) {
return Math.pow(Math.abs(a), 2);
}
console.log(f(-1), f(2));
}
expect_stdout: "1 4"
}

View File

@@ -862,3 +862,20 @@ issue_2749: {
}
expect_stdout: "PASS"
}
unsafe_builtin: {
options = {
side_effects: true,
unsafe: true,
}
input: {
(!w).constructor(x);
Math.abs(y);
[ 1, 2, z ].valueOf();
}
expect: {
w, x;
y;
z;
}
}

View File

@@ -1692,3 +1692,30 @@ issue_2768: {
}
expect_stdout: "PASS undefined"
}
issue_2846: {
options = {
collapse_vars: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f(a, b) {
var a = 0;
b && b(a);
return a++;
}
var c = f();
console.log(c);
}
expect: {
var c = function(a, b) {
a = 0;
b && b(a);
return a++;
}();
console.log(c);
}
expect_stdout: "0"
}

View File

@@ -1194,6 +1194,9 @@ issue_2231_1: {
console.log(Object.keys(void 0));
}
expect_stdout: true
expect_warnings: [
"WARN: Error evaluating Object.keys(void 0) [test/compress/evaluate.js:1191,20]",
]
}
issue_2231_2: {
@@ -1208,6 +1211,23 @@ issue_2231_2: {
console.log(Object.getOwnPropertyNames(null));
}
expect_stdout: true
expect_warnings: [
"WARN: Error evaluating Object.getOwnPropertyNames(null) [test/compress/evaluate.js:1208,20]",
]
}
issue_2231_3: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log(Object.keys({ foo: "bar" })[0]);
}
expect: {
console.log("foo");
}
expect_stdout: "foo"
}
self_comparison_1: {
@@ -1330,13 +1350,27 @@ issue_2535_3: {
}
expect_stdout: true
expect_warnings: [
"WARN: Dropping side-effect-free && [test/compress/evaluate.js:1316,20]",
"WARN: Dropping side-effect-free && [test/compress/evaluate.js:1317,20]",
"WARN: Dropping side-effect-free && [test/compress/evaluate.js:1318,20]",
"WARN: Condition left of && always false [test/compress/evaluate.js:1318,20]",
"WARN: Dropping side-effect-free || [test/compress/evaluate.js:1319,20]",
"WARN: Dropping side-effect-free || [test/compress/evaluate.js:1320,20]",
"WARN: Dropping side-effect-free || [test/compress/evaluate.js:1321,20]",
"WARN: Condition left of || always true [test/compress/evaluate.js:1321,20]",
"WARN: Dropping side-effect-free && [test/compress/evaluate.js:1336,20]",
"WARN: Dropping side-effect-free && [test/compress/evaluate.js:1337,20]",
"WARN: Dropping side-effect-free && [test/compress/evaluate.js:1338,20]",
"WARN: Condition left of && always false [test/compress/evaluate.js:1338,20]",
"WARN: Dropping side-effect-free || [test/compress/evaluate.js:1339,20]",
"WARN: Dropping side-effect-free || [test/compress/evaluate.js:1340,20]",
"WARN: Dropping side-effect-free || [test/compress/evaluate.js:1341,20]",
"WARN: Condition left of || always true [test/compress/evaluate.js:1341,20]",
]
}
issue_2822: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log([ function() {}, "PASS", "FAIL" ][1]);
}
expect: {
console.log("PASS");
}
expect_stdout: "PASS"
}

View File

@@ -218,7 +218,7 @@ issue_203: {
options = {
keep_fargs: false,
side_effects: true,
unsafe_Func: true,
unsafe_Function: true,
unused: true,
}
input: {
@@ -1951,3 +1951,41 @@ issue_2737_2: {
}
expect_stdout: "PASS"
}
issue_2783: {
options = {
collapse_vars: true,
conditionals: true,
if_return: true,
inline: true,
reduce_vars: true,
unused: true,
}
input: {
(function() {
return g;
function f(a) {
var b = a.b;
if (b) return b;
return a;
}
function g(o, i) {
while (i--) {
console.log(f(o));
}
}
})()({ b: "PASS" }, 1);
}
expect: {
(function() {
return function(o,i) {
while (i--) console.log(f(o));
};
function f(a) {
var b = a.b;
return b || a;
}
})()({ b: "PASS" },1);
}
expect_stdout: "PASS"
}

View File

@@ -664,3 +664,25 @@ issue_2519: {
}
expect_stdout: "5.5"
}
undefined_key: {
options = {
evaluate: true,
hoist_props: true,
join_vars: true,
passes: 4,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a, o = {};
o[a] = 1;
o.b = 2;
console.log(o[a] + o.b);
}
expect: {
console.log(3);
}
expect_stdout: "3"
}

View File

@@ -4,7 +4,7 @@ unsafe_undefined: {
options = {
conditionals: true,
if_return: true,
unsafe: true
unsafe_undefined: true,
}
mangle = {}
input: {
@@ -30,7 +30,7 @@ keep_fnames: {
options = {
conditionals: true,
if_return: true,
unsafe: true
unsafe_undefined: true,
}
mangle = {
keep_fnames: true

View File

@@ -61,7 +61,7 @@ unsafe_undefined: {
options = {
conditionals: true,
if_return: true,
unsafe: true,
unsafe_undefined: true,
}
mangle = {}
input: {

View File

@@ -84,12 +84,12 @@ numeric_literal: {
' 0: 0,',
' "-0": 1,',
' 42: 2,',
' "42": 3,',
' 42: 3,',
' 37: 4,',
' o: 5,',
' 1e42: 6,',
' b: 7,',
' "1e+42": 8',
' 1e42: 8',
'};',
'',
'console.log(obj[-0], obj[-""], obj["-0"]);',

View File

@@ -583,6 +583,25 @@ native_prototype: {
}
}
native_prototype_lhs: {
options = {
unsafe_proto: true,
}
input: {
console.log(function() {
Function.prototype.bar = "PASS";
return function() {};
}().bar);
}
expect: {
console.log(function() {
Function.prototype.bar = "PASS";
return function() {};
}().bar);
}
expect_stdout: "PASS"
}
accessor_boolean: {
input: {
var a = 1;
@@ -1188,3 +1207,384 @@ join_object_assignments_3: {
}
expect_stdout: "PASS"
}
join_object_assignments_return_1: {
options = {
join_vars: true,
}
input: {
console.log(function() {
var o = {
p: 3
};
return o.q = "foo";
}());
}
expect: {
console.log(function() {
var o = {
p: 3,
q: "foo"
};
return o.q;
}());
}
expect_stdout: "foo"
}
join_object_assignments_return_2: {
options = {
join_vars: true,
}
input: {
console.log(function() {
var o = {
p: 3
};
return o.q = /foo/,
o.r = "bar";
}());
}
expect: {
console.log(function() {
var o = {
p: 3,
q: /foo/,
r: "bar"
};
return o.r;
}());
}
expect_stdout: "bar"
}
join_object_assignments_return_3: {
options = {
join_vars: true,
}
input: {
console.log(function() {
var o = {
p: 3
};
return o.q = "foo",
o.p += "",
console.log(o.q),
o.p;
}());
}
expect: {
console.log(function() {
var o = {
p: 3,
q: "foo"
};
return o.p += "",
console.log(o.q),
o.p;
}());
}
expect_stdout: [
"foo",
"3",
]
}
join_object_assignments_for: {
options = {
join_vars: true,
}
input: {
console.log(function() {
var o = {
p: 3
};
for (o.q = "foo"; console.log(o.q););
return o.p;
}());
}
expect: {
console.log(function() {
for (var o = {
p: 3,
q: "foo"
}; console.log(o.q););
return o.p;
}());
}
expect_stdout: [
"foo",
"3",
]
}
join_object_assignments_if: {
options = {
join_vars: true,
}
input: {
console.log(function() {
var o = {};
if (o.a = "PASS") return o.a;
}())
}
expect: {
console.log(function() {
var o = { a: "PASS" };
if (o.a) return o.a;
}());
}
expect_stdout: "PASS"
}
join_object_assignments_forin: {
options = {
join_vars: true,
}
input: {
console.log(function() {
var o = {};
for (var a in o.a = "PASS", o)
return o[a];
}())
}
expect: {
console.log(function() {
var o = { a: "PASS" };
for (var a in o)
return o[a];
}());
}
expect_stdout: "PASS"
}
join_object_assignments_negative: {
options = {
evaluate: true,
join_vars: true,
properties: true,
}
input: {
var o = {};
o[0] = 0;
o[-0] = 1;
o[-1] = 2;
console.log(o[0], o[-0], o[-1]);
}
expect: {
var o = {
0: 0,
0: 1,
"-1": 2
};
console.log(o[0], o[-0], o[-1]);
}
expect_stdout: "1 1 2"
}
join_object_assignments_NaN_1: {
options = {
join_vars: true,
}
input: {
var o = {};
o[NaN] = 1;
o[0/0] = 2;
console.log(o[NaN], o[NaN]);
}
expect: {
var o = {};
o[NaN] = 1;
o[0/0] = 2;
console.log(o[NaN], o[NaN]);
}
expect_stdout: "2 2"
}
join_object_assignments_NaN_2: {
options = {
evaluate: true,
join_vars: true,
properties: true,
}
input: {
var o = {};
o[NaN] = 1;
o[0/0] = 2;
console.log(o[NaN], o[NaN]);
}
expect: {
var o = {
NaN: 1,
NaN: 2
};
console.log(o.NaN, o.NaN);
}
expect_stdout: "2 2"
}
join_object_assignments_null_0: {
options = {
join_vars: true,
}
input: {
var o = {};
o[null] = 1;
console.log(o[null]);
}
expect: {
var o = {};
o[null] = 1;
console.log(o[null]);
}
expect_stdout: "1"
}
join_object_assignments_null_1: {
options = {
evaluate: true,
join_vars: true,
properties: true,
}
input: {
var o = {};
o[null] = 1;
console.log(o[null]);
}
expect: {
var o = {
null: 1
};
console.log(o.null);
}
expect_stdout: "1"
}
join_object_assignments_void_0: {
options = {
evaluate: true,
join_vars: true,
}
input: {
var o = {};
o[void 0] = 1;
console.log(o[void 0]);
}
expect: {
var o = {
undefined: 1
};
console.log(o[void 0]);
}
expect_stdout: "1"
}
join_object_assignments_undefined_1: {
options = {
join_vars: true,
}
input: {
var o = {};
o[undefined] = 1;
console.log(o[undefined]);
}
expect: {
var o = {};
o[void 0] = 1;
console.log(o[void 0]);
}
expect_stdout: "1"
}
join_object_assignments_undefined_2: {
options = {
evaluate: true,
join_vars: true,
properties: true,
}
input: {
var o = {};
o[undefined] = 1;
console.log(o[undefined]);
}
expect: {
var o = {
undefined : 1
};
console.log(o[void 0]);
}
expect_stdout: "1"
}
join_object_assignments_Infinity: {
options = {
evaluate: true,
join_vars: true,
properties: true,
}
input: {
var o = {};
o[Infinity] = 1;
o[1/0] = 2;
o[-Infinity] = 3;
o[-1/0] = 4;
console.log(o[Infinity], o[1/0], o[-Infinity], o[-1/0]);
}
expect: {
var o = {
Infinity: 1,
Infinity: 2,
"-Infinity": 3,
"-Infinity": 4
};
console.log(o[1/0], o[1/0], o[-1/0], o[-1/0]);
}
expect_stdout: "2 2 4 4"
}
join_object_assignments_regex: {
options = {
evaluate: true,
join_vars: true,
properties: true,
}
input: {
var o = {};
o[/rx/] = 1;
console.log(o[/rx/]);
}
expect: {
var o = {
"/rx/": 1
};
console.log(o[/rx/]);
}
expect_stdout: "1"
}
issue_2816: {
options = {
join_vars: true,
}
input: {
"use strict";
var o = {
a: 1
};
o.b = 2;
o.a = 3;
o.c = 4;
console.log(o.a, o.b, o.c);
}
expect: {
"use strict";
var o = {
a: 1,
b: 2
};
o.a = 3;
o.c = 4;
console.log(o.a, o.b, o.c);
}
expect_stdout: "3 2 4"
}

View File

@@ -694,3 +694,30 @@ issue_2678: {
}
expect_stdout: "PASS"
}
issue_2838: {
options = {
pure_getters: true,
side_effects: true,
}
input: {
function f(a, b) {
(a || b).c = "PASS";
(function() {
return f(a, b);
}).prototype.foo = "bar";
}
var o = {};
f(null, o);
console.log(o.c);
}
expect: {
function f(a, b) {
(a || b).c = "PASS";
}
var o = {};
f(null, o);
console.log(o.c);
}
expect_stdout: "PASS"
}

View File

@@ -5302,3 +5302,84 @@ issue_2774: {
}
expect_stdout: "undefined"
}
issue_2799_1: {
options = {
reduce_funcs: true,
reduce_vars: true,
unused: true,
}
input: {
console.log(function() {
return f;
function f(n) {
function g(i) {
return i && i + g(i - 1);
}
function h(j) {
return g(j);
}
return h(n);
}
}()(5));
}
expect: {
console.log(function() {
return function(n) {
return function(j) {
return function g(i) {
return i && i + g(i - 1);
}(j);
}(n);
}
}()(5));
}
expect_stdout: "15"
}
issue_2799_2: {
options = {
reduce_vars: true,
unsafe_proto: true,
unused: true,
}
input: {
(function() {
function foo() {
Function.prototype.call.apply(console.log, [ null, "PASS" ]);
}
foo();
})();
}
expect: {
(function() {
(function() {
(function() {}).call.apply(console.log, [ null, "PASS" ]);
})();
})();
}
expect_stdout: "PASS"
}
issue_2836: {
options = {
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f() {
return "FAIL";
}
console.log(f());
function f() {
return "PASS";
}
}
expect: {
console.log(function() {
return "PASS";
}());
}
expect_stdout: "PASS"
}

View File

@@ -288,7 +288,7 @@ unsafe_undefined: {
if_return: true,
sequences: true,
side_effects: true,
unsafe: true,
unsafe_undefined: true,
}
input: {
function f(undefined) {
@@ -859,3 +859,21 @@ for_init_var: {
}
expect_stdout: "PASS"
}
forin: {
options = {
sequences: true,
}
input: {
var o = [];
o.push("PASS");
for (var a in o)
console.log(o[a]);
}
expect: {
var o = [];
for (var a in o.push("PASS"), o)
console.log(o[a]);
}
expect_stdout: "PASS"
}

View File

@@ -0,0 +1,6 @@
function f(x) {
return g(x);
function g(x) {
return x;
}
}

View File

@@ -1,29 +1,24 @@
var Mocha = require('mocha'),
fs = require('fs'),
path = require('path');
var fs = require("fs");
var Mocha = require("mocha");
var path = require("path");
// Instantiate a Mocha instance.
var mocha = new Mocha({});
// Instantiate a Mocha instance
var mocha = new Mocha({
timeout: 5000
});
var testDir = __dirname + "/mocha/";
var testDir = __dirname + '/mocha/';
// Add each .js file to the mocha instance
fs.readdirSync(testDir).filter(function(file){
// Only keep the .js files
return file.substr(-3) === '.js';
}).forEach(function(file){
mocha.addFile(
path.join(testDir, file)
);
// Add each .js file to the Mocha instance
fs.readdirSync(testDir).filter(function(file) {
return /\.js$/.test(file);
}).forEach(function(file) {
mocha.addFile(path.join(testDir, file));
});
module.exports = function() {
mocha.run(function(failures) {
if (failures !== 0) {
process.on('exit', function () {
process.exit(failures);
});
}
if (failures) process.on("exit", function() {
process.exit(failures);
});
});
};
};

View File

@@ -650,4 +650,36 @@ describe("bin/uglifyjs", function () {
done();
});
});
it("Should work with explicit --rename", function(done) {
var command = uglifyjscmd + " test/input/rename/input.js --rename";
exec(command, function(err, stdout, stderr) {
if (err) throw err;
assert.strictEqual(stdout, "function f(a){return b(a);function b(c){return c}}\n");
done();
});
});
it("Should work with explicit --no-rename", function(done) {
var command = uglifyjscmd + " test/input/rename/input.js -mc --no-rename";
exec(command, function(err, stdout, stderr) {
if (err) throw err;
assert.strictEqual(stdout, "function f(n){return function(n){return n}(n)}\n");
done();
});
});
it("Should work with implicit --rename", function(done) {
var command = uglifyjscmd + " test/input/rename/input.js -mc";
exec(command, function(err, stdout, stderr) {
if (err) throw err;
assert.strictEqual(stdout, "function f(n){return n}\n");
done();
});
});
it("Should work with implicit --no-rename", function(done) {
var command = uglifyjscmd + " test/input/rename/input.js -c";
exec(command, function(err, stdout, stderr) {
if (err) throw err;
assert.strictEqual(stdout, "function f(x){return function(x){return x}(x)}\n");
done();
});
});
});

View File

@@ -17,24 +17,30 @@ function safe_log(arg, level) {
return arg;
}
function strip_func_ids(text) {
return text.toString().replace(/F[0-9]{6}N/g, "<F<>N>");
}
var FUNC_TOSTRING = [
"[ Array, Boolean, Error, Function, Number, Object, RegExp, String].forEach(function(f) {",
" f.toString = Function.prototype.toString;",
" f.valueOf = Function.prototype.valueOf;",
"});",
"Function.prototype.toString = Function.prototype.valueOf = function() {",
" var id = 100000;",
" return function() {",
' if (this === Array) return "[Function: Array]";',
' if (this === Object) return "[Function: Object]";',
" var i = this.name;",
' if (typeof i != "number") {',
" i = ++id;",
" var n = this.name;",
' if (!/^F[0-9]{6}N$/.test(n)) {',
' n = "F" + ++id + "N";',
].concat(Object.getOwnPropertyDescriptor(Function.prototype, "name").configurable ? [
' Object.defineProperty(this, "name", {',
" get: function() {",
" return i;",
" return n;",
" }",
" });",
] : [], [
" }",
' return "[Function: " + i + "]";',
' return "[Function: " + n + "]";',
" }",
"}();",
'Object.defineProperty(Function.prototype, "valueOf", { enumerable: false });',
@@ -77,7 +83,7 @@ exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expec
expected = expected.message.slice(expected.message.lastIndexOf("\n") + 1);
actual = actual.message.slice(actual.message.lastIndexOf("\n") + 1);
}
return expected == actual;
return strip_func_ids(expected) == strip_func_ids(actual);
} : function(expected, actual) {
return typeof expected == typeof actual && expected.toString() == actual.toString();
return typeof expected == typeof actual && strip_func_ids(expected) == strip_func_ids(actual);
};

82
test/travis-ufuzz.js Normal file
View File

@@ -0,0 +1,82 @@
"use strict";
var child_process = require("child_process");
var https = require("https");
var url = require("url");
var period = 45 * 60 * 1000;
var wait = 2 * 60 * 1000;
var ping = 5 * 60 * 1000;
if (process.argv[2] == "run") {
var endTime = Date.now() + period;
for (var i = 0; i < 2; i++) spawn(endTime);
} else if (process.argv.length > 2) {
var token = process.argv[2];
var branch = process.argv[3] || "v" + require("../package.json").version;
var repository = encodeURIComponent(process.argv[4] || "mishoo/UglifyJS2");
var concurrency = process.argv[5] || 1;
(function request() {
setTimeout(request, (period + wait) / concurrency);
var options = url.parse("https://api.travis-ci.org/repo/" + repository + "/requests");
options.method = "POST";
options.headers = {
"Content-Type": "application/json",
"Travis-API-Version": 3,
"Authorization": "token " + token
};
https.request(options, function(res) {
console.log("HTTP", res.statusCode);
console.log(JSON.stringify(res.headers, null, 2));
console.log();
res.setEncoding("utf8");
res.on("data", console.log);
}).on("error", console.error).end(JSON.stringify({
request: {
message: "ufuzz testing (when idle)",
branch: branch,
config: {
merge_mode: "replace",
language: "node_js",
node_js: "9",
sudo: false,
script: "node test/travis-ufuzz run"
}
}
}));
})();
} else {
console.log("Usage: test/travis-ufuzz.js <token> [branch] [repository] [concurrency]");
}
function spawn(endTime) {
var child = child_process.spawn("node", [
"--max-old-space-size=2048",
"test/ufuzz"
], {
stdio: [ "ignore", "pipe", "pipe" ]
}).on("exit", respawn);
var line = "";
child.stdout.on("data", function(data) {
line += data;
});
child.stderr.on("data", function() {
process.exitCode = 1;
}).pipe(process.stdout);
var keepAlive = setInterval(function() {
var end = line.lastIndexOf("\r");
console.log(line.slice(line.lastIndexOf("\r", end - 1) + 1, end));
line = line.slice(end + 1);
}, ping);
var timer = setTimeout(function() {
clearInterval(keepAlive);
child.removeListener("exit", respawn);
child.kill();
}, endTime - Date.now());
function respawn() {
console.log(line);
clearInterval(keepAlive);
clearTimeout(timer);
spawn(endTime);
}
}

View File

@@ -998,10 +998,11 @@ function log_suspects(minify_options, component) {
if (typeof options != "object") options = {};
var defs = default_options[component];
var suspects = Object.keys(defs).filter(function(name) {
if ((name in options ? options : defs)[name]) {
var flip = name == "keep_fargs";
if (flip ? name in options : (name in options ? options : defs)[name]) {
var m = JSON.parse(JSON.stringify(minify_options));
var o = JSON.parse(JSON.stringify(options));
o[name] = false;
o[name] = flip;
m[component] = o;
var result = UglifyJS.minify(original_code, m);
if (result.error) {
@@ -1022,6 +1023,24 @@ function log_suspects(minify_options, component) {
}
}
function log_rename(options) {
if (!options.rename) return;
var m = JSON.parse(JSON.stringify(options));
m.rename = false;
var result = UglifyJS.minify(original_code, m);
if (result.error) {
errorln("Error testing options.rename");
errorln(result.error.stack);
} else {
var r = sandbox.run_code(result.code);
if (sandbox.same_stdout(original_result, r)) {
errorln("Suspicious options:");
errorln(" rename");
errorln();
}
}
}
function log(options) {
if (!ok) errorln('\n\n\n\n\n\n!!!!!!!!!!\n\n\n');
errorln("//=============================================================");
@@ -1056,6 +1075,7 @@ function log(options) {
errorln();
if (!ok && typeof uglify_code == "string") {
Object.keys(default_options).forEach(log_suspects.bind(null, options));
log_rename(options);
errorln("!!!!!! Failed... round " + round);
}
}

View File

@@ -5,7 +5,8 @@
"output": {
"beautify": true,
"bracketize": true
}
},
"rename": true
},
{
"compress": false
@@ -20,7 +21,13 @@
{
"compress": {
"keep_fargs": false,
"passes": 100
"passes": 1e6,
"sequences": 1e6,
"unsafe": true,
"unsafe_Function": true,
"unsafe_math": true,
"unsafe_proto": true,
"unsafe_regexp": true
}
}
]