Compare commits

..

21 Commits

Author SHA1 Message Date
Alex Lam S.L
ac7b5c07d7 v3.12.6 2021-02-01 01:37:32 +08:00
Alex Lam S.L
0cd4a199b0 fix corner case in conditionals (#4599)
fixes #4598
2021-01-30 16:54:29 +08:00
Alex Lam S.L
35435d4bd3 suppress false positives due to nested objects (#4597) 2021-01-29 13:21:19 +08:00
Alex Lam S.L
d0bb147639 fix corner case in inline (#4596)
fixes #4595
2021-01-27 01:30:05 +08:00
Alex Lam S.L
4723b4541e workaround tty bugs on Node.js (#4594) 2021-01-26 23:07:48 +08:00
Alex Lam S.L
9d23ba0a22 support exponentiation operator (#4593) 2021-01-25 05:48:51 +08:00
Alex Lam S.L
a08d42555a fix infinite recursion in ufuzz code generation (#4592) 2021-01-24 23:37:57 +08:00
Alex Lam S.L
fd7ad8e779 fix corner cases in collapse_vars (#4591)
fixes #4590
2021-01-24 22:15:43 +08:00
Alex Lam S.L
a36c5472d2 fix corner cases with default parameters (#4589)
fixes #4588
2021-01-24 11:00:47 +08:00
Alex Lam S.L
8bfd891c09 support BigInt literals (#4583) 2021-01-24 09:51:18 +08:00
Alex Lam S.L
ef9f7ca3e7 fix corner case in collapse_vars (#4587)
fixes #4586
2021-01-24 07:05:43 +08:00
Alex Lam S.L
acc443b2cf fix corner case in reduce_vars (#4585)
fixes #4584
2021-01-24 03:37:52 +08:00
Alex Lam S.L
f87e7be12c fix corner case in reduce_vars (#4582)
fixes #4581
2021-01-23 02:14:53 +08:00
Alex Lam S.L
c0614654d9 improve ufuzz on destructuring (#4580) 2021-01-23 02:00:26 +08:00
Alex Lam S.L
0358637725 workaround Node.js bug (#4579) 2021-01-22 11:34:30 +08:00
Alex Lam S.L
63b5b6d2b3 suppress false positives in ufuzz (#4578) 2021-01-22 02:33:00 +08:00
Alex Lam S.L
e675262d51 suppress false positives in ufuzz (#4577) 2021-01-21 14:33:31 +08:00
Alex Lam S.L
c1e771a89a fix corner case in rests (#4576)
fixes #4575
2021-01-21 07:23:06 +08:00
Alex Lam S.L
bc7a88baea suppress false positives in ufuzz (#4574) 2021-01-20 21:03:33 +08:00
Alex Lam S.L
018e0350f8 workaround GitHub Actions bug (#4573) 2021-01-20 20:17:58 +08:00
Alex Lam S.L
d37ee4d41c support asynchronous test cases properly (#4529) 2021-01-20 07:27:32 +08:00
27 changed files with 1155 additions and 417 deletions

View File

@@ -32,6 +32,8 @@ jobs:
- uses: actions/checkout@v2
- name: Install GNU Core Utilities
if: ${{ startsWith(matrix.os, 'macos') }}
env:
HOMEBREW_NO_INSTALL_CLEANUP: 1
shell: bash
run: |
brew install coreutils

View File

@@ -1254,3 +1254,9 @@ To allow for better optimizations, the compiler makes various assumptions:
}()) => b)());
```
UglifyJS may modify the input which in turn may suppress those errors.
- Some arithmetic operations with `BigInt` may throw `TypeError`:
```javascript
1n + 1;
// TypeError: can't convert BigInt to number
```
UglifyJS may modify the input which in turn may suppress those errors.

View File

@@ -249,7 +249,7 @@ var AST_BlockScope = DEFNODE("BlockScope", "enclosed functions make_def parent_s
enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes",
functions: "[Object/S] like `variables`, but only lists function declarations",
parent_scope: "[AST_Scope?/S] link to the parent scope",
variables: "[Object/S] a map of name -> SymbolDef for all variables/functions defined in this scope",
variables: "[Object/S] a map of name ---> SymbolDef for all variables/functions defined in this scope",
},
clone: function(deep) {
var node = this._clone(deep);
@@ -472,7 +472,7 @@ var AST_Scope = DEFNODE("Scope", "uses_eval uses_with", {
var AST_Toplevel = DEFNODE("Toplevel", "globals", {
$documentation: "The toplevel scope",
$propdoc: {
globals: "[Object/S] a map of name -> SymbolDef for all undeclared names",
globals: "[Object/S] a map of name ---> SymbolDef for all undeclared names",
},
wrap: function(name) {
var body = this.body;
@@ -1440,6 +1440,19 @@ var AST_Number = DEFNODE("Number", "value", {
},
_validate: function() {
if (typeof this.value != "number") throw new Error("value must be number");
if (!isFinite(this.value)) throw new Error("value must be finite");
if (this.value < 0) throw new Error("value cannot be negative");
},
}, AST_Constant);
var AST_BigInt = DEFNODE("BigInt", "value", {
$documentation: "A BigInt literal",
$propdoc: {
value: "[string] the numeric representation",
},
_validate: function() {
if (typeof this.value != "string") throw new Error("value must be string");
if (this.value[0] == "-") throw new Error("value cannot be negative");
},
}, AST_Constant);

View File

@@ -382,6 +382,7 @@ merge(Compressor.prototype, {
});
}
var RE_POSITIVE_INTEGER = /^(0|[1-9][0-9]*)$/;
(function(def) {
def(AST_Node, noop);
@@ -603,7 +604,7 @@ merge(Compressor.prototype, {
if (!is_arguments(def)) return;
var key = node.property;
if (key.is_constant()) key = key.value;
if (!(key instanceof AST_Node) && !/^[1-9]*[0-9]$/.test(key)) return;
if (!(key instanceof AST_Node) && !RE_POSITIVE_INTEGER.test(key)) return;
def.reassigned = true;
(key instanceof AST_Node ? def.scope.argnames : [ def.scope.argnames[key] ]).forEach(function(argname) {
if (argname instanceof AST_SymbolFunarg) argname.definition().fixed = false;
@@ -718,7 +719,7 @@ merge(Compressor.prototype, {
var fn = this;
fn.inlined = false;
var iife = tw.parent();
var hit = fn instanceof AST_AsyncFunction;
var hit = is_async(fn);
var aborts = false;
fn.walk(new TreeWalker(function(node) {
if (hit) return aborts = true;
@@ -728,7 +729,7 @@ merge(Compressor.prototype, {
if (aborts) push(tw);
reset_variables(tw, compressor, fn);
// Virtually turn IIFE parameters into variable definitions:
// (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})()
// (function(a,b) {...})(c,d) ---> (function() {var a=c,b=d; ...})()
// So existing transformation rules can work on them.
var safe = !fn.uses_arguments || tw.has_directive("use strict");
fn.argnames.forEach(function(arg, i) {
@@ -785,7 +786,10 @@ merge(Compressor.prototype, {
}
var d = sym.definition();
d.assignments++;
if (fixed && !is_modified(compressor, tw, node, node.right, 0) && safe_to_assign(tw, d)) {
if (fixed
&& !is_modified(compressor, tw, node, node.right, 0)
&& !sym.in_arg
&& safe_to_assign(tw, d)) {
push_ref(d, sym);
mark(tw, d);
if (d.single_use && left instanceof AST_Destructured) d.single_use = false;
@@ -808,7 +812,7 @@ merge(Compressor.prototype, {
}
var safe = safe_to_read(tw, d);
node.right.walk(tw);
if (safe && safe_to_assign(tw, d)) {
if (safe && !left.in_arg && safe_to_assign(tw, d)) {
push_ref(d, left);
mark(tw, d);
if (d.single_use) d.single_use = false;
@@ -1116,7 +1120,7 @@ merge(Compressor.prototype, {
var d = exp.definition();
d.assignments++;
var fixed = d.fixed;
if (safe_to_read(tw, d) && safe_to_assign(tw, d)) {
if (safe_to_read(tw, d) && !exp.in_arg && safe_to_assign(tw, d)) {
push_ref(d, exp);
mark(tw, d);
if (d.single_use) d.single_use = false;
@@ -2341,6 +2345,7 @@ merge(Compressor.prototype, {
if (expr.operator == "="
&& lhs instanceof AST_SymbolRef
&& (def = lhs.definition()).references[0] === lhs
&& !(scope.uses_arguments && is_funarg(def))
&& !compressor.exposed(def)) {
var referenced = def.references.length - def.replaced;
if (referenced > 1) mangleable_var(expr.right);
@@ -2353,6 +2358,7 @@ merge(Compressor.prototype, {
var def = expr.name.definition();
if (def.const_redefs) return;
if (!member(expr.name, def.orig)) return;
if (scope.uses_arguments && is_funarg(def)) return;
var declared = def.orig.length - def.eliminated - (declare_only[def.name] || 0);
var referenced = def.references.length - def.replaced - (assignments[def.name] || 0);
if (declared > 1 && !(expr.name instanceof AST_SymbolFunarg)) {
@@ -2407,7 +2413,8 @@ merge(Compressor.prototype, {
if (expr instanceof AST_This) return rhs_exact_match;
if (expr.is_truthy()) return rhs_fuzzy_match(true, return_false);
if (expr.is_constant()) {
return rhs_fuzzy_match(expr.evaluate(compressor), rhs_exact_match);
var ev = expr.evaluate(compressor);
if (!(ev instanceof AST_Node)) return rhs_fuzzy_match(ev, rhs_exact_match);
}
if (!(lhs instanceof AST_SymbolRef)) return false;
if (!invariant(expr)) return false;
@@ -2430,7 +2437,8 @@ merge(Compressor.prototype, {
return true;
}
if (node.is_constant()) {
return !node.evaluate(compressor) == !value;
var ev = node.evaluate(compressor);
if (!(ev instanceof AST_Node)) return !ev == !value;
}
}
return fallback(node);
@@ -2736,7 +2744,7 @@ merge(Compressor.prototype, {
var in_bool = stat.body.in_bool || next instanceof AST_Return && next.in_bool;
//---
// pretty silly case, but:
// if (foo()) return; return; => foo(); return;
// if (foo()) return; return; ---> foo(); return;
if (!value && !stat.alternative
&& (in_lambda && !next || next instanceof AST_Return && !next.value)) {
CHANGED = true;
@@ -2746,7 +2754,7 @@ merge(Compressor.prototype, {
continue;
}
//---
// if (foo()) return x; return y; => return foo() ? x : y;
// if (foo()) return x; return y; ---> return foo() ? x : y;
if (!stat.alternative && next instanceof AST_Return) {
CHANGED = true;
stat = stat.clone();
@@ -2756,7 +2764,7 @@ merge(Compressor.prototype, {
continue;
}
//---
// if (foo()) return x; [ return ; ] => return foo() ? x : undefined;
// if (foo()) return x; [ return ; ] ---> return foo() ? x : undefined;
if (!stat.alternative && !next && in_lambda && (in_bool || value && multiple_if_returns)) {
CHANGED = true;
stat = stat.clone();
@@ -2767,7 +2775,7 @@ merge(Compressor.prototype, {
continue;
}
//---
// if (a) return b; if (c) return d; e; => return a ? b : c ? d : void e;
// if (a) return b; if (c) return d; e; ---> return a ? b : c ? d : void e;
//
// if sequences is not enabled, this can lead to an endless loop (issue #866).
// however, with sequences on this helps producing slightly better output for
@@ -3811,6 +3819,14 @@ merge(Compressor.prototype, {
return skip_directives(this.body);
});
AST_Lambda.DEFMETHOD("length", function() {
var argnames = this.argnames;
for (var i = 0; i < argnames.length; i++) {
if (argnames[i] instanceof AST_DefaultValue) break;
}
return i;
});
function try_evaluate(compressor, node) {
var ev = node.evaluate(compressor);
if (ev === node) return node;
@@ -3964,6 +3980,7 @@ merge(Compressor.prototype, {
throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
});
def(AST_Accessor, return_this);
def(AST_BigInt, return_this);
def(AST_Node, return_this);
def(AST_Constant, function() {
return this.value;
@@ -4151,6 +4168,9 @@ merge(Compressor.prototype, {
case "<=" : result = left <= right; break;
case ">" : result = left > right; break;
case ">=" : result = left >= right; break;
case "**":
result = Math.pow(left, right);
break;
case "in":
if (right && typeof right == "object" && HOP(right, left)) {
result = true;
@@ -4166,7 +4186,7 @@ merge(Compressor.prototype, {
&& typeof result == "number"
&& (this.operator == "+" || this.operator == "-")) {
var digits = Math.max(0, decimals(left), decimals(right));
// 53-bit significand => 15.95 decimal places
// 53-bit significand ---> 15.95 decimal places
if (digits < 16) return +result.toFixed(digits);
}
return result;
@@ -4270,7 +4290,7 @@ merge(Compressor.prototype, {
case "name":
return val.node.name ? val.node.name.name : "";
case "length":
return val.node.argnames.length;
return val.node.length();
default:
return this;
}
@@ -4893,10 +4913,10 @@ merge(Compressor.prototype, {
return self;
});
function trim_block(node) {
function trim_block(node, in_list) {
switch (node.body.length) {
case 0:
return make_node(AST_EmptyStatement, node);
return in_list ? List.skip : make_node(AST_EmptyStatement, node);
case 1:
var stat = node.body[0];
if (!(stat instanceof AST_Const || stat instanceof AST_Let)) return stat;
@@ -5698,19 +5718,33 @@ merge(Compressor.prototype, {
unused_fn_names.push(node);
}
if (!(node instanceof AST_Accessor)) {
if (node.rest) {
node.rest = node.rest.transform(trimmer);
if (!(node.uses_arguments && !tt.has_directive("use strict"))
&& (node.rest instanceof AST_DestructuredArray && node.rest.elements.length == 0
|| node.rest instanceof AST_DestructuredObject && node.rest.properties.length == 0)) {
node.rest = null;
}
}
var argnames = node.argnames;
var trim = compressor.drop_fargs(node, parent) && !node.rest;
for (var a = node.argnames, i = a.length; --i >= 0;) {
var sym = a[i];
var default_length = trim ? -1 : node.length();
for (var i = argnames.length; --i >= 0;) {
var sym = argnames[i];
if (!(sym instanceof AST_SymbolFunarg)) {
var arg = sym.transform(trimmer);
if (arg) {
trim = false;
} else if (trim) {
log(sym.name, "Dropping unused function argument {name}");
a.pop();
} else {
log(sym.name, "Dropping unused default argument {name}");
argnames.pop();
} else if (i > default_length) {
log(sym.name, "Dropping unused default argument assignment {name}");
sym.name.__unused = true;
a[i] = sym.name;
argnames[i] = sym.name;
} else {
log(sym.name, "Dropping unused default argument value {name}");
sym.value = make_node(AST_Number, sym, { value: 0 });
}
continue;
}
@@ -5720,19 +5754,11 @@ merge(Compressor.prototype, {
if (indexOf_assign(def, sym) < 0) sym.__unused = null;
} else if (trim) {
log(sym, "Dropping unused function argument {name}");
a.pop();
argnames.pop();
} else {
sym.__unused = true;
}
}
if (node.rest) {
node.rest = node.rest.transform(trimmer);
if (!(node.uses_arguments && !tt.has_directive("use strict"))
&& (node.rest instanceof AST_DestructuredArray && node.rest.elements.length == 0
|| node.rest instanceof AST_DestructuredObject && node.rest.properties.length == 0)) {
node.rest = null;
}
}
fns_with_marked_args.push(node);
}
}
@@ -5957,12 +5983,8 @@ merge(Compressor.prototype, {
return node;
}
}, function(node, in_list) {
if (node instanceof AST_BlockStatement) switch (node.body.length) {
case 0:
return in_list ? List.skip : make_node(AST_EmptyStatement, node);
case 1:
var stat = node.body[0];
if (!(stat instanceof AST_Const || stat instanceof AST_Let)) return stat;
if (node instanceof AST_BlockStatement) {
return trim_block(node, in_list);
} else if (node instanceof AST_For) {
// Certain combination of unused name + side effect leads to invalid AST:
// https://github.com/mishoo/UglifyJS/issues/44
@@ -7489,7 +7511,7 @@ merge(Compressor.prototype, {
var exprs = [];
for (var i = 0; i < stat.body.length; i++) {
var line = stat.body[i];
if (line instanceof AST_Defun) {
if (is_defun(line)) {
defuns.push(line);
} else if (line instanceof AST_EmptyStatement) {
continue;
@@ -7506,7 +7528,7 @@ merge(Compressor.prototype, {
}
return exprs;
}
if (stat instanceof AST_Defun) {
if (is_defun(stat)) {
defuns.push(stat);
return [];
}
@@ -7844,11 +7866,11 @@ merge(Compressor.prototype, {
if (fn.rest) {
if (!(is_iife && compressor.option("rests"))) return;
var insert = fn.argnames.length;
for (var i = args.length; i < insert; i++) {
args[i] = make_node(AST_Undefined, call).optimize(compressor);
}
args[insert] = make_node(AST_Array, call, { elements: args.splice(insert) });
fn.argnames.push(fn.rest);
args = args.slice(0, insert);
while (args.length < insert) args.push(make_node(AST_Undefined, call).optimize(compressor));
args.push(make_node(AST_Array, call, { elements: call.args.slice(insert) }));
call.args = args;
fn.argnames = fn.argnames.concat(fn.rest);
fn.rest = null;
}
var pos = 0, last = 0;
@@ -7938,6 +7960,7 @@ merge(Compressor.prototype, {
if (compressor.option("unsafe")) {
if (is_undeclared_ref(exp)) switch (exp.name) {
case "Array":
// Array(n) ---> [ , , ... , ]
if (self.args.length == 1) {
var first = self.args[0];
if (first instanceof AST_Number) try {
@@ -7945,9 +7968,7 @@ merge(Compressor.prototype, {
if (length > 6) break;
var elements = Array(length);
for (var i = 0; i < length; i++) elements[i] = make_node(AST_Hole, self);
return make_node(AST_Array, self, {
elements: elements
});
return make_node(AST_Array, self, { elements: elements });
} catch (ex) {
AST_Node.warn("Invalid array length: {length} [{file}:{line},{col}]", {
length: length,
@@ -7959,81 +7980,90 @@ merge(Compressor.prototype, {
}
if (!first.is_boolean(compressor) && !first.is_string(compressor)) break;
}
return make_node(AST_Array, self, {
elements: self.args
});
// Array(...) ---> [ ... ]
return make_node(AST_Array, self, { elements: self.args });
case "Object":
if (self.args.length == 0) {
return make_node(AST_Object, self, {
properties: []
});
}
// Object() ---> {}
if (self.args.length == 0) return make_node(AST_Object, self, { properties: [] });
break;
case "String":
if (self.args.length == 0) return make_node(AST_String, self, {
value: ""
});
if (self.args.length <= 1) return make_node(AST_Binary, self, {
left: self.args[0],
// String() ---> ""
if (self.args.length == 0) return make_node(AST_String, self, { value: "" });
// String(x) ---> "" + x
if (self.args.length == 1) return make_node(AST_Binary, self, {
operator: "+",
right: make_node(AST_String, self, { value: "" })
left: make_node(AST_String, self, { value: "" }),
right: self.args[0],
}).optimize(compressor);
break;
case "Number":
if (self.args.length == 0) return make_node(AST_Number, self, {
value: 0
});
// Number() ---> 0
if (self.args.length == 0) return make_node(AST_Number, self, { value: 0 });
// Number(x) ---> +("" + x)
if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
expression: self.args[0],
operator: "+"
}).optimize(compressor);
case "Boolean":
if (self.args.length == 0) return make_node(AST_False, self);
if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
expression: make_node(AST_UnaryPrefix, self, {
expression: self.args[0],
operator: "!"
operator: "+",
expression: make_node(AST_Binary, self, {
operator: "+",
left: make_node(AST_String, self, { value: "" }),
right: self.args[0],
}),
}).optimize(compressor);
break;
case "Boolean":
// Boolean() ---> false
if (self.args.length == 0) return make_node(AST_False, self).optimize(compressor);
// Boolean(x) ---> !!x
if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
operator: "!",
expression: make_node(AST_UnaryPrefix, self, {
operator: "!",
expression: self.args[0],
}),
operator: "!"
}).optimize(compressor);
break;
case "RegExp":
// attempt to convert RegExp(...) to literal
var params = [];
if (all(self.args, function(arg) {
var value = arg.evaluate(compressor);
params.unshift(value);
return arg !== value;
})) {
try {
return best_of(compressor, self, make_node(AST_RegExp, self, {
value: RegExp.apply(RegExp, params),
}));
} catch (ex) {
AST_Node.warn("Error converting {expr} [{file}:{line},{col}]", {
expr: self,
file: self.start.file,
line: self.start.line,
col: self.start.col,
});
}
})) try {
return best_of(compressor, self, make_node(AST_RegExp, self, {
value: RegExp.apply(RegExp, params),
}));
} catch (ex) {
AST_Node.warn("Error converting {expr} [{file}:{line},{col}]", {
expr: self,
file: self.start.file,
line: self.start.line,
col: self.start.col,
});
}
break;
} else if (exp instanceof AST_Dot) switch(exp.property) {
} else if (exp instanceof AST_Dot) switch (exp.property) {
case "toString":
// x.toString() ---> "" + x
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
left: make_node(AST_String, self, { value: "" }),
right: exp.expression,
}).optimize(compressor);
}
break;
case "join":
if (exp.expression instanceof AST_Array) EXIT: {
var separator;
if (self.args.length > 0) {
separator = self.args[0].evaluate(compressor);
if (separator === self.args[0]) break EXIT; // not a constant
if (exp.expression instanceof AST_Array && self.args.length < 2) EXIT: {
var separator = self.args[0];
// [].join() ---> ""
// [].join(x) ---> (x, "")
if (exp.expression.elements.length == 0) return separator ? make_sequence(self, [
separator,
make_node(AST_String, self, { value: "" }),
]).optimize(compressor) : make_node(AST_String, self, { value: "" });
if (separator) {
separator = separator.evaluate(compressor);
if (separator instanceof AST_Node) break EXIT; // not a constant
}
var elements = [];
var consts = [];
@@ -8044,45 +8074,46 @@ merge(Compressor.prototype, {
} else {
if (consts.length > 0) {
elements.push(make_node(AST_String, self, {
value: consts.join(separator)
value: consts.join(separator),
}));
consts.length = 0;
}
elements.push(el);
}
});
if (consts.length > 0) {
elements.push(make_node(AST_String, self, {
value: consts.join(separator)
}));
}
if (elements.length == 0) return make_node(AST_String, self, { value: "" });
if (consts.length > 0) elements.push(make_node(AST_String, self, {
value: consts.join(separator),
}));
// [ x ].join() ---> "" + x
// [ x ].join(".") ---> "" + x
// [ 1, 2, 3 ].join() ---> "1,2,3"
// [ 1, 2, 3 ].join(".") ---> "1.2.3"
if (elements.length == 1) {
if (elements[0].is_string(compressor)) {
return elements[0];
}
if (elements[0].is_string(compressor)) return elements[0];
return make_node(AST_Binary, elements[0], {
operator : "+",
left : make_node(AST_String, self, { value: "" }),
right : elements[0]
operator: "+",
left: make_node(AST_String, self, { value: "" }),
right: elements[0],
});
}
// [ 1, 2, a, 3 ].join("") ---> "12" + a + "3"
if (separator == "") {
var first;
if (elements[0].is_string(compressor)
|| elements[1].is_string(compressor)) {
if (elements[0].is_string(compressor) || elements[1].is_string(compressor)) {
first = elements.shift();
} else {
first = make_node(AST_String, self, { value: "" });
}
return elements.reduce(function(prev, el) {
return make_node(AST_Binary, el, {
operator : "+",
left : prev,
right : el
operator: "+",
left: prev,
right: el,
});
}, first).optimize(compressor);
}
// [ x, "foo", "bar", y ].join() ---> [ x, "foo,bar", y ].join()
// [ x, "foo", "bar", y ].join("-") ---> [ x, "foo-bar", y ].join("-")
// need this awkward cloning to not affect original element
// best_of will decide which one to get through.
var node = self.clone();
@@ -8146,7 +8177,7 @@ merge(Compressor.prototype, {
if (compressor.option("unsafe_Function")
&& is_undeclared_ref(exp)
&& exp.name == "Function") {
// new Function() => function(){}
// new Function() ---> function(){}
if (self.args.length == 0) return make_node(AST_Function, self, {
argnames: [],
body: []
@@ -8160,7 +8191,7 @@ merge(Compressor.prototype, {
try {
var code = "n(function(" + self.args.slice(0, -1).map(function(arg) {
return arg.value;
}).join(",") + "){" + self.args[self.args.length - 1].value + "})";
}).join() + "){" + self.args[self.args.length - 1].value + "})";
var ast = parse(code);
var mangle = { ie8: compressor.option("ie8") };
ast.figure_out_scope(mangle);
@@ -8183,7 +8214,7 @@ merge(Compressor.prototype, {
make_node(AST_String, self, {
value: fun.argnames.map(function(arg) {
return arg.print_to_string();
}).join(",")
}).join(),
}),
make_node(AST_String, self.args[self.args.length - 1], {
value: code.get().replace(/^\{|\}$/g, "")
@@ -8235,7 +8266,7 @@ merge(Compressor.prototype, {
if (can_inline
&& !fn.uses_arguments
&& !fn.pinned()
&& !(fn.name && fn instanceof AST_Function)
&& !(fn.name && is_function(fn))
&& (exp === fn || !recursive_ref(compressor, def = exp.definition())
&& fn.is_constant_expression(find_scope(compressor)))
&& !has_spread
@@ -8774,8 +8805,8 @@ merge(Compressor.prototype, {
return self;
});
// (a = b, x && a = c) => a = x ? c : b
// (a = b, x || a = c) => a = x ? b : c
// (a = b, x && a = c) ---> a = x ? c : b
// (a = b, x || a = c) ---> a = x ? b : c
function to_conditional_assignment(compressor, def, value, node) {
if (!(node instanceof AST_Binary)) return;
if (!lazy_op[node.operator]) return;
@@ -8906,7 +8937,7 @@ merge(Compressor.prototype, {
} else if (compressor.in_boolean_context()) switch (op) {
case "!":
if (exp instanceof AST_UnaryPrefix && exp.operator == "!") {
// !!foo => foo, if we're in boolean context
// !!foo ---> foo, if we're in boolean context
return exp.expression;
}
if (exp instanceof AST_Binary) {
@@ -9071,8 +9102,8 @@ merge(Compressor.prototype, {
}
if (compressor.option("assignments") && lazy_op[self.operator]) {
var assign = self.right;
// a || (a = x) => a = a || x
// a && (a = x) => a = a && x
// a || (a = x) ---> a = a || x
// a && (a = x) ---> a = a && x
if (self.left instanceof AST_SymbolRef
&& assign instanceof AST_Assign
&& assign.operator == "="
@@ -9102,11 +9133,11 @@ merge(Compressor.prototype, {
// XXX: intentionally falling down to the next case
case "==":
case "!=":
// void 0 == x => null == x
// void 0 == x ---> null == x
if (!is_strict_comparison && is_undefined(self.left, compressor)) {
self.left = make_node(AST_Null, self.left);
}
// "undefined" == typeof x => undefined === x
// "undefined" == typeof x ---> undefined === x
else if (compressor.option("typeofs")
&& self.left instanceof AST_String
&& self.left.value == "undefined"
@@ -9120,7 +9151,7 @@ merge(Compressor.prototype, {
if (self.operator.length == 2) self.operator += "=";
}
}
// obj !== obj => false
// obj !== obj ---> false
else if (self.left instanceof AST_SymbolRef
&& self.right instanceof AST_SymbolRef
&& self.left.definition() === self.right.definition()
@@ -9130,8 +9161,8 @@ merge(Compressor.prototype, {
break;
case "&&":
case "||":
// void 0 !== x && null !== x => null != x
// void 0 === x || null === x => null == x
// void 0 !== x && null !== x ---> null != x
// void 0 === x || null === x ---> null == x
var lhs = self.left;
if (lhs.operator == self.operator) {
lhs = lhs.right;
@@ -9208,8 +9239,8 @@ merge(Compressor.prototype, {
case ">=": reverse("<="); break;
}
}
// x && (y && z) => x && y && z
// x || (y || z) => x || y || z
// x && (y && z) ---> x && y && z
// x || (y || z) ---> x || y || z
if (compressor.option("conditionals")
&& lazy_op[self.operator]
&& self.right instanceof AST_Binary
@@ -9217,19 +9248,19 @@ merge(Compressor.prototype, {
swap_chain();
}
if (compressor.option("strings") && self.operator == "+") {
// "foo" + 42 + "" => "foo" + 42
// "foo" + 42 + "" ---> "foo" + 42
if (self.right instanceof AST_String
&& self.right.value == ""
&& self.left.is_string(compressor)) {
return self.left.optimize(compressor);
}
// "" + ("foo" + 42) => "foo" + 42
// "" + ("foo" + 42) ---> "foo" + 42
if (self.left instanceof AST_String
&& self.left.value == ""
&& self.right.is_string(compressor)) {
return self.right.optimize(compressor);
}
// "" + 42 + "foo" => 42 + "foo"
// "" + 42 + "foo" ---> 42 + "foo"
if (self.left instanceof AST_Binary
&& self.left.operator == "+"
&& self.left.left instanceof AST_String
@@ -9238,8 +9269,8 @@ merge(Compressor.prototype, {
self.left = self.left.right;
return self.optimize(compressor);
}
// "x" + (y + "z") => "x" + y + "z"
// x + ("y" + z) => x + "y" + z
// "x" + (y + "z") ---> "x" + y + "z"
// x + ("y" + z) ---> x + "y" + z
if (self.right instanceof AST_Binary
&& self.operator == self.right.operator
&& (self.left.is_string(compressor) && self.right.is_string(compressor)
@@ -9275,7 +9306,7 @@ merge(Compressor.prototype, {
return self.left.optimize(compressor);
}
}
// (x || false) && y => x ? y : false
// (x || false) && y ---> x ? y : false
if (self.left.operator == "||") {
var lr = self.left.right.evaluate(compressor, true);
if (!lr) return make_node(AST_Conditional, self, {
@@ -9309,7 +9340,7 @@ merge(Compressor.prototype, {
]).optimize(compressor);
} else self.truthy = true;
}
// x && true || y => x ? true : y
// x && true || y ---> x ? true : y
if (self.left.operator == "&&") {
var lr = self.left.right.is_truthy() || self.left.right.evaluate(compressor, true);
if (lr && !(lr instanceof AST_Node)) return make_node(AST_Conditional, self, {
@@ -9320,7 +9351,7 @@ merge(Compressor.prototype, {
}
break;
case "+":
// "foo" + ("bar" + x) => "foobar" + x
// "foo" + ("bar" + x) ---> "foobar" + x
if (self.left instanceof AST_Constant
&& self.right instanceof AST_Binary
&& self.right.operator == "+"
@@ -9336,7 +9367,7 @@ merge(Compressor.prototype, {
right: self.right.right
});
}
// (x + "foo") + "bar" => x + "foobar"
// (x + "foo") + "bar" ---> x + "foobar"
if (self.right instanceof AST_Constant
&& self.left instanceof AST_Binary
&& self.left.operator == "+"
@@ -9352,7 +9383,7 @@ merge(Compressor.prototype, {
})
});
}
// a + -b => a - b
// a + -b ---> a - b
if (self.right instanceof AST_UnaryPrefix
&& self.right.operator == "-"
&& self.left.is_number(compressor)) {
@@ -9363,7 +9394,7 @@ merge(Compressor.prototype, {
});
break;
}
// -a + b => b - a
// -a + b ---> b - a
if (self.left instanceof AST_UnaryPrefix
&& self.left.operator == "-"
&& reversible()
@@ -9375,7 +9406,7 @@ merge(Compressor.prototype, {
});
break;
}
// (a + b) + 3 => 3 + (a + b)
// (a + b) + 3 ---> 3 + (a + b)
if (compressor.option("unsafe_math")
&& self.left instanceof AST_Binary
&& PRECEDENCE[self.left.operator] == PRECEDENCE[self.operator]
@@ -9396,7 +9427,7 @@ merge(Compressor.prototype, {
break;
}
case "-":
// a - -b => a + b
// a - -b ---> a + b
if (self.right instanceof AST_UnaryPrefix
&& self.right.operator == "-"
&& self.left.is_number(compressor)
@@ -9411,8 +9442,8 @@ merge(Compressor.prototype, {
case "*":
case "/":
associative = compressor.option("unsafe_math");
// +a - b => a - b
// a - +b => a - b
// +a - b ---> a - b
// a - +b ---> a - b
if (self.operator != "+") [ "left", "right" ].forEach(function(operand) {
var node = self[operand];
if (node instanceof AST_UnaryPrefix && node.operator == "+") {
@@ -9425,7 +9456,7 @@ merge(Compressor.prototype, {
case "&":
case "|":
case "^":
// a + +b => +b + a
// a + +b ---> +b + a
if (self.operator != "-"
&& self.operator != "/"
&& (self.left.is_boolean(compressor) || self.left.is_number(compressor))
@@ -9447,7 +9478,7 @@ merge(Compressor.prototype, {
}
}
if (!associative || !self.is_number(compressor)) break;
// a + (b + c) => (a + b) + c
// a + (b + c) ---> (a + b) + c
if (self.right instanceof AST_Binary
&& self.right.operator != "%"
&& PRECEDENCE[self.right.operator] == PRECEDENCE[self.operator]
@@ -9479,8 +9510,8 @@ merge(Compressor.prototype, {
});
}
}
// (2 * n) * 3 => 6 * n
// (n + 2) + 3 => n + 5
// (2 * n) * 3 ---> 6 * n
// (n + 2) + 3 ---> n + 5
if (self.right instanceof AST_Constant
&& self.left instanceof AST_Binary
&& self.left.operator != "%"
@@ -9505,7 +9536,7 @@ merge(Compressor.prototype, {
}
if (!(parent instanceof AST_UnaryPrefix && parent.operator == "delete")) {
if (self.left instanceof AST_Number && !self.right.is_constant()) switch (self.operator) {
// 0 + n => n
// 0 + n ---> n
case "+":
if (self.left.value == 0) {
if (self.right.is_boolean(compressor)) return make_node(AST_UnaryPrefix, self, {
@@ -9515,7 +9546,7 @@ merge(Compressor.prototype, {
if (self.right.is_number(compressor) && !self.right.is_negative_zero()) return self.right;
}
break;
// 1 * n => n
// 1 * n ---> n
case "*":
if (self.left.value == 1) {
return self.right.is_number(compressor) ? self.right : make_node(AST_UnaryPrefix, self, {
@@ -9526,7 +9557,7 @@ merge(Compressor.prototype, {
break;
}
if (self.right instanceof AST_Number && !self.left.is_constant()) switch (self.operator) {
// n + 0 => n
// n + 0 ---> n
case "+":
if (self.right.value == 0) {
if (self.left.is_boolean(compressor)) return make_node(AST_UnaryPrefix, self, {
@@ -9536,7 +9567,7 @@ merge(Compressor.prototype, {
if (self.left.is_number(compressor) && !self.left.is_negative_zero()) return self.left;
}
break;
// n - 0 => n
// n - 0 ---> n
case "-":
if (self.right.value == 0) {
return self.left.is_number(compressor) ? self.left : make_node(AST_UnaryPrefix, self, {
@@ -9545,7 +9576,7 @@ merge(Compressor.prototype, {
}).optimize(compressor);
}
break;
// n / 1 => n
// n / 1 ---> n
case "/":
if (self.right.value == 1) {
return self.left.is_number(compressor) ? self.left : make_node(AST_UnaryPrefix, self, {
@@ -9668,16 +9699,16 @@ merge(Compressor.prototype, {
function is_indexOf_match_pattern() {
switch (self.operator) {
case "<=":
// 0 <= array.indexOf(string) => !!~array.indexOf(string)
// 0 <= array.indexOf(string) ---> !!~array.indexOf(string)
return indexRight && self.left instanceof AST_Number && self.left.value == 0;
case "<":
// array.indexOf(string) < 0 => !~array.indexOf(string)
// array.indexOf(string) < 0 ---> !~array.indexOf(string)
if (indexLeft && self.right instanceof AST_Number && self.right.value == 0) return true;
// -1 < array.indexOf(string) => !!~array.indexOf(string)
// -1 < array.indexOf(string) ---> !!~array.indexOf(string)
case "==":
case "!=":
// -1 == array.indexOf(string) => !~array.indexOf(string)
// -1 != array.indexOf(string) => !!~array.indexOf(string)
// -1 == array.indexOf(string) ---> !~array.indexOf(string)
// -1 != array.indexOf(string) ---> !!~array.indexOf(string)
if (!indexRight) return false;
return self.left instanceof AST_Number && self.left.value == -1
|| self.left instanceof AST_UnaryPrefix && self.left.operator == "-"
@@ -10031,7 +10062,7 @@ merge(Compressor.prototype, {
if (self.right.left instanceof AST_SymbolRef
&& self.right.left.name == self.left.name
&& ASSIGN_OPS[self.right.operator]) {
// x = x - 2 => x -= 2
// x = x - 2 ---> x -= 2
return make_node(AST_Assign, self, {
operator: self.right.operator + "=",
left: self.left,
@@ -10042,7 +10073,7 @@ merge(Compressor.prototype, {
&& self.right.right.name == self.left.name
&& ASSIGN_OPS_COMMUTATIVE[self.right.operator]
&& !self.right.left.has_side_effects(compressor)) {
// x = 2 & x => x &= 2
// x = 2 & x ---> x &= 2
return make_node(AST_Assign, self, {
operator: self.right.operator + "=",
left: self.left,
@@ -10116,13 +10147,13 @@ merge(Compressor.prototype, {
var consequent = self.consequent;
var alternative = self.alternative;
if (repeatable(compressor, condition)) {
// x ? x : y => x || y
// x ? x : y ---> x || y
if (condition.equivalent_to(consequent)) return make_node(AST_Binary, self, {
operator: "||",
left: condition,
right: alternative,
}).optimize(compressor);
// x ? y : x => x && y
// x ? y : x ---> x && y
if (condition.equivalent_to(alternative)) return make_node(AST_Binary, self, {
operator: "&&",
left: condition,
@@ -10156,17 +10187,17 @@ merge(Compressor.prototype, {
});
}
}
// x ? y : y => x, y
// x ? y : y ---> x, y
if (consequent.equivalent_to(alternative)) return make_sequence(self, [
condition,
consequent
]).optimize(compressor);
// x ? y.p : z.p => (x ? y : z).p
// x ? y(a) : z(a) => (x ? y : z)(a)
// x ? y.f(a) : z.f(a) => (x ? y : z).f(a)
// x ? y.p : z.p ---> (x ? y : z).p
// x ? y(a) : z(a) ---> (x ? y : z)(a)
// x ? y.f(a) : z.f(a) ---> (x ? y : z).f(a)
var combined = combine_tail(consequent, alternative, true);
if (combined) return combined;
// x ? y(a) : y(b) => y(x ? a : b)
// x ? y(a) : y(b) ---> y(x ? a : b)
var arg_index;
if (consequent instanceof AST_Call
&& alternative.TYPE == consequent.TYPE
@@ -10189,7 +10220,7 @@ merge(Compressor.prototype, {
});
return node;
}
// x ? (y ? a : b) : b => x && y ? a : b
// x ? (y ? a : b) : b ---> x && y ? a : b
if (consequent instanceof AST_Conditional
&& consequent.alternative.equivalent_to(alternative)) {
return make_node(AST_Conditional, self, {
@@ -10202,7 +10233,7 @@ merge(Compressor.prototype, {
alternative: alternative
});
}
// x ? (y ? a : b) : a => !x || y ? a : b
// x ? (y ? a : b) : a ---> !x || y ? a : b
if (consequent instanceof AST_Conditional
&& consequent.consequent.equivalent_to(alternative)) {
return make_node(AST_Conditional, self, {
@@ -10215,7 +10246,7 @@ merge(Compressor.prototype, {
alternative: consequent.alternative
});
}
// x ? a : (y ? a : b) => x || y ? a : b
// x ? a : (y ? a : b) ---> x || y ? a : b
if (alternative instanceof AST_Conditional
&& consequent.equivalent_to(alternative.consequent)) {
return make_node(AST_Conditional, self, {
@@ -10228,7 +10259,7 @@ merge(Compressor.prototype, {
alternative: alternative.alternative
});
}
// x ? b : (y ? a : b) => !x && y ? a : b
// x ? b : (y ? a : b) ---> !x && y ? a : b
if (alternative instanceof AST_Conditional
&& consequent.equivalent_to(alternative.alternative)) {
return make_node(AST_Conditional, self, {
@@ -10241,7 +10272,7 @@ merge(Compressor.prototype, {
alternative: consequent
});
}
// x ? (a, c) : (b, c) => x ? a : b, c
// x ? (a, c) : (b, c) ---> x ? a : b, c
if ((consequent instanceof AST_Sequence || alternative instanceof AST_Sequence)
&& consequent.tail_node().equivalent_to(alternative.tail_node())) {
return make_sequence(self, [
@@ -10253,7 +10284,7 @@ merge(Compressor.prototype, {
consequent.tail_node()
]).optimize(compressor);
}
// x ? y && a : a => (!x || y) && a
// x ? y && a : a ---> (!x || y) && a
if (consequent instanceof AST_Binary
&& consequent.operator == "&&"
&& consequent.right.equivalent_to(alternative)) {
@@ -10267,7 +10298,7 @@ merge(Compressor.prototype, {
right: alternative
}).optimize(compressor);
}
// x ? y || a : a => x && y || a
// x ? y || a : a ---> x && y || a
if (consequent instanceof AST_Binary
&& consequent.operator == "||"
&& consequent.right.equivalent_to(alternative)) {
@@ -10281,7 +10312,7 @@ merge(Compressor.prototype, {
right: alternative
}).optimize(compressor);
}
// x ? a : y && a => (x || y) && a
// x ? a : y && a ---> (x || y) && a
if (alternative instanceof AST_Binary
&& alternative.operator == "&&"
&& alternative.right.equivalent_to(consequent)) {
@@ -10295,7 +10326,7 @@ merge(Compressor.prototype, {
right: consequent
}).optimize(compressor);
}
// x ? a : y || a => !x && y || a
// x ? a : y || a ---> !x && y || a
if (alternative instanceof AST_Binary
&& alternative.operator == "||"
&& alternative.right.equivalent_to(consequent)) {
@@ -10312,10 +10343,10 @@ merge(Compressor.prototype, {
var in_bool = compressor.option("booleans") && compressor.in_boolean_context();
if (is_true(consequent)) {
if (is_false(alternative)) {
// c ? true : false => !!c
// c ? true : false ---> !!c
return booleanize(condition);
}
// c ? true : x => !!c || x
// c ? true : x ---> !!c || x
return make_node(AST_Binary, self, {
operator: "||",
left: booleanize(condition),
@@ -10324,10 +10355,10 @@ merge(Compressor.prototype, {
}
if (is_false(consequent)) {
if (is_true(alternative)) {
// c ? false : true => !c
// c ? false : true ---> !c
return booleanize(condition.negate(compressor));
}
// c ? false : x => !c && x
// c ? false : x ---> !c && x
return make_node(AST_Binary, self, {
operator: "&&",
left: booleanize(condition.negate(compressor)),
@@ -10335,7 +10366,7 @@ merge(Compressor.prototype, {
});
}
if (is_true(alternative)) {
// c ? x : true => !c || x
// c ? x : true ---> !c || x
return make_node(AST_Binary, self, {
operator: "||",
left: booleanize(condition.negate(compressor)),
@@ -10343,7 +10374,7 @@ merge(Compressor.prototype, {
});
}
if (is_false(alternative)) {
// c ? x : false => !!c && x
// c ? x : false ---> !!c && x
return make_node(AST_Binary, self, {
operator: "&&",
left: booleanize(condition),
@@ -10846,7 +10877,7 @@ merge(Compressor.prototype, {
flush();
values.push(prop);
}
if (found && !generated && typeof key == "string" && /^[1-9]*[0-9]$/.test(key)) {
if (found && !generated && typeof key == "string" && RE_POSITIVE_INTEGER.test(key)) {
generated = true;
if (keys.has(key)) prop = keys.get(key)[0];
prop.key = make_node(AST_Number, prop, {

View File

@@ -684,37 +684,41 @@ function OutputStream(options) {
PARENS(AST_Unary, function(output) {
var p = output.parent();
// (-x) ** y
if (p instanceof AST_Binary) return p.operator == "**" && p.left === this;
// (x++).toString(3)
// (typeof x).length
return (p instanceof AST_Call || p instanceof AST_PropAccess) && p.expression === this;
});
PARENS(AST_Sequence, function(output) {
var p = output.parent();
// [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ]
// [ 1, (2, 3), 4 ] ---> [ 1, 3, 4 ]
return p instanceof AST_Array
// () => (foo, bar)
// () ---> (foo, bar)
|| is_arrow(p) && p.value === this
// await (foo, bar)
|| p instanceof AST_Await
// 1 + (2, 3) + 4 ==> 8
// 1 + (2, 3) + 4 ---> 8
|| p instanceof AST_Binary
// new (foo, bar) or foo(1, (2, 3), 4)
|| p instanceof AST_Call
// (false, true) ? (a = 10, b = 20) : (c = 30)
// ==> 20 (side effect, set a := 10 and b := 20)
// ---> 20 (side effect, set a := 10 and b := 20)
|| p instanceof AST_Conditional
// [ a = (1, 2) ] = [] ==> a == 2
// [ a = (1, 2) ] = [] ---> a == 2
|| p instanceof AST_DefaultValue
// { [(1, 2)]: 3 }[2] ==> 3
// { foo: (1, 2) }.foo ==> 2
// { [(1, 2)]: 3 }[2] ---> 3
// { foo: (1, 2) }.foo ---> 2
|| p instanceof AST_DestructuredKeyVal
|| p instanceof AST_ObjectProperty
// (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2
// (1, {foo:2}).foo or (1, {foo:2})["foo"] ---> 2
|| p instanceof AST_PropAccess && p.expression === this
// ...(foo, bar, baz)
|| p instanceof AST_Spread
// !(foo, bar, baz)
|| p instanceof AST_Unary
// var a = (1, 2), b = a + a; ==> b == 4
// var a = (1, 2), b = a + a; ---> b == 4
|| p instanceof AST_VarDef;
});
@@ -722,11 +726,14 @@ function OutputStream(options) {
var p = output.parent();
// await (foo && bar)
if (p instanceof AST_Await) return true;
// this deals with precedence: 3 * (2 + 1)
// this deals with precedence:
// 3 * (2 + 1)
// 3 - (2 - 1)
// (1 ** 2) ** 3
if (p instanceof AST_Binary) {
var po = p.operator, pp = PRECEDENCE[po];
var so = this.operator, sp = PRECEDENCE[so];
return pp > sp || (pp == sp && this === p.right);
return pp > sp || (pp == sp && this === p[po == "**" ? "left" : "right"]);
}
// (foo && bar)()
if (p instanceof AST_Call) return p.expression === this;
@@ -777,14 +784,10 @@ function OutputStream(options) {
});
PARENS(AST_Number, function(output) {
if (!output.option("galio")) return false;
// https://github.com/mishoo/UglifyJS/pull/1009
var p = output.parent();
if (p instanceof AST_PropAccess && p.expression === this) {
var value = this.value;
// https://github.com/mishoo/UglifyJS/issues/115
return value < 0
// https://github.com/mishoo/UglifyJS/pull/1009
|| output.option("galio") && /^0/.test(make_num(value));
}
return p instanceof AST_PropAccess && p.expression === this && /^0/.test(make_num(this.value));
});
function needs_parens_assign_cond(self, output) {
@@ -807,8 +810,8 @@ function OutputStream(options) {
});
PARENS(AST_Assign, function(output) {
if (needs_parens_assign_cond(this, output)) return true;
// v8 parser bug => workaround
// f([1], [a] = []) => f([1], ([a] = []))
// v8 parser bug ---> workaround
// f([1], [a] = []) ---> f([1], ([a] = []))
if (output.option("v8")) return this.left instanceof AST_Destructured;
// ({ p: a } = o);
if (this.left instanceof AST_DestructuredObject) return needs_parens_obj(output);
@@ -822,6 +825,8 @@ function OutputStream(options) {
PARENS(AST_Await, function(output) {
var p = output.parent();
// (await x) ** y
if (p instanceof AST_Binary) return p.operator == "**" && p.left === this;
// new (await foo)
// (await foo)(bar)
if (p instanceof AST_Call) return p.expression === this;

View File

@@ -81,6 +81,7 @@ var OPERATORS = makePredicate([
"*",
"/",
"%",
"**",
">>",
"<<",
">>>",
@@ -280,9 +281,8 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
}
function read_while(pred) {
var ret = "", ch, i = 0;
while ((ch = peek()) && pred(ch, i++))
ret += next();
var ret = "", ch;
while ((ch = peek()) && pred(ch)) ret += next();
return ret;
}
@@ -292,16 +292,14 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
function read_num(prefix) {
var has_e = false, after_e = false, has_x = false, has_dot = prefix == ".";
var num = read_while(function(ch, i) {
var num = read_while(function(ch) {
var code = ch.charCodeAt(0);
switch (code) {
case 120: case 88: // xX
return has_x ? false : (has_x = true);
case 101: case 69: // eE
return has_x ? true : has_e ? false : (has_e = after_e = true);
case 45: // -
return after_e || (i == 0 && !prefix);
case 43: // +
case 43: case 45: // +-
return after_e;
case (after_e = false, 46): // .
return (!has_dot && !has_x && !has_e) ? (has_dot = true) : false;
@@ -315,8 +313,9 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
num = num.replace(has_x ? /([1-9a-f]|.0)_(?=[0-9a-f])/gi : /([1-9]|.0)_(?=[0-9])/gi, "$1");
}
var valid = parse_js_number(num);
if (!isNaN(valid)) return token("num", valid);
parse_error("Invalid syntax: " + num);
if (isNaN(valid)) parse_error("Invalid syntax: " + num);
if (has_dot || has_e || peek() != "n") return token("num", valid);
return token("bigint", num.toLowerCase() + next());
}
function read_escaped_char(in_string) {
@@ -632,10 +631,11 @@ var PRECEDENCE = function(a, ret) {
["<", ">", "<=", ">=", "in", "instanceof"],
[">>", "<<", ">>>"],
["+", "-"],
["*", "/", "%"]
["*", "/", "%"],
["**"],
], {});
var ATOMIC_START_TOKEN = makePredicate("atom num regexp string");
var ATOMIC_START_TOKEN = makePredicate("atom bigint num regexp string");
/* -----[ Parser ]----- */
@@ -783,6 +783,7 @@ function parse($TEXT, options) {
semicolon();
return dir ? new AST_Directive(body) : new AST_SimpleStatement({ body: body });
case "num":
case "bigint":
case "regexp":
case "operator":
case "atom":
@@ -1361,6 +1362,9 @@ function parse($TEXT, options) {
case "num":
ret = new AST_Number({ start: tok, end: tok, value: tok.value });
break;
case "bigint":
ret = new AST_BigInt({ start: tok, end: tok, value: tok.value });
break;
case "string":
ret = new AST_String({
start : tok,
@@ -1858,7 +1862,7 @@ function parse($TEXT, options) {
var prec = op != null ? PRECEDENCE[op] : null;
if (prec != null && prec > min_prec) {
next();
var right = expr_op(maybe_await(), prec, no_in);
var right = expr_op(maybe_await(), op == "**" ? prec - 1 : prec, no_in);
return expr_op(new AST_Binary({
start : left.start,
left : left,

View File

@@ -228,7 +228,7 @@ function mangle_properties(ast, options) {
var mangled = cache.get(name);
if (!mangled) {
if (debug) {
// debug mode: use a prefix and suffix to preserve readability, e.g. o.foo -> o._$foo$NNN_.
// debug mode: use a prefix and suffix to preserve readability, e.g. o.foo ---> o._$foo$NNN_.
var debug_mangled = "_$" + name + "$" + debug_suffix + "_";
if (can_mangle(debug_mangled)) mangled = debug_mangled;
}

View File

@@ -3,7 +3,7 @@
"description": "JavaScript parser, mangler/compressor and beautifier toolkit",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause",
"version": "3.12.5",
"version": "3.12.6",
"engines": {
"node": ">=0.8.0"
},

View File

@@ -209,7 +209,7 @@ function reminify(orig_options, input_code, input_formatted, stdout) {
} else {
var toplevel = sandbox.has_toplevel(options);
var expected = stdout[toplevel ? 1 : 0];
var actual = run_code(result.code, toplevel);
var actual = sandbox.run_code(result.code, toplevel);
if (typeof expected != "string" && typeof actual != "string" && expected.name == actual.name) {
actual = expected;
}
@@ -244,11 +244,6 @@ function reminify(orig_options, input_code, input_formatted, stdout) {
return true;
}
function run_code(code, toplevel) {
var result = sandbox.run_code(code, toplevel);
return typeof result == "string" ? result.replace(/\u001b\[\d+m/g, "") : result;
}
function test_case(test) {
log(" Running test [{name}]", { name: test.name });
U.AST_Node.enable_validation();
@@ -380,7 +375,7 @@ function test_case(test) {
}
}
if (test.expect_stdout && (!test.node_version || semver.satisfies(process.version, test.node_version))) {
var stdout = [ run_code(input_code), run_code(input_code, true) ];
var stdout = [ sandbox.run_code(input_code), sandbox.run_code(input_code, true) ];
var toplevel = sandbox.has_toplevel({
compress: test.options,
mangle: test.mangle
@@ -409,7 +404,7 @@ function test_case(test) {
});
return false;
}
actual = run_code(output_code, toplevel);
actual = sandbox.run_code(output_code, toplevel);
if (!sandbox.same_stdout(test.expect_stdout, actual)) {
log([
"!!! failed",

View File

@@ -13,9 +13,10 @@ holes_and_undefined: {
}
}
constant_join: {
constant_join_1: {
options = {
evaluate: true,
side_effects: true,
strings: true,
unsafe: true,
}
@@ -57,7 +58,7 @@ constant_join: {
var c5 = [ boo() + bar() + "foo", 1, 2, 3, "bar", bar() + "foo" ].join();
var c6 = [ "1,2,,,foo,bar", baz() ].join();
var d = "foo-3bar-baz";
var e = [].join(foo + bar);
var e = (foo, bar, "");
var f = "";
var g = "";
}

View File

@@ -612,22 +612,32 @@ issue_4340: {
call_expression: {
input: {
console.log(typeof async function(log) {
(await log)("FAIL");
(await log)("foo");
}(console.log).then);
console.log("bar");
}
expect_exact: 'console.log(typeof async function(log){(await log)("FAIL")}(console.log).then);'
expect_stdout: "function"
expect_exact: 'console.log(typeof async function(log){(await log)("foo")}(console.log).then);console.log("bar");'
expect_stdout: [
"function",
"bar",
"foo",
]
node_version: ">=8"
}
property_access_expression: {
input: {
console.log(typeof async function(con) {
(await con).log("FAIL");
(await con).log("foo");
}(console).then);
console.log("bar");
}
expect_exact: 'console.log(typeof async function(con){(await con).log("FAIL")}(console).then);'
expect_stdout: "function"
expect_exact: 'console.log(typeof async function(con){(await con).log("foo")}(console).then);console.log("bar");'
expect_stdout: [
"function",
"bar",
"foo",
]
node_version: ">=8"
}
@@ -685,20 +695,18 @@ reduce_iife_3: {
input: {
var a = "foo";
(async function() {
console.log(a);
console.log(await a);
console.log(a, await a, a, await a);
})();
a = "bar";
}
expect: {
var a = "foo";
(async function() {
console.log(a);
console.log(await a);
console.log(a, await a, a, await a);
})();
a = "bar";
}
expect_stdout: "foo"
expect_stdout: "foo foo bar bar"
node_version: ">=8"
}
@@ -995,3 +1003,64 @@ issue_4534: {
expect_stdout: "PASS"
node_version: ">=8"
}
issue_4581: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a = "PASS";
(async () => (A, a = "FAIL"))();
console.log(a);
}
expect: {
var a = "PASS";
(async () => (A, a = "FAIL"))();
console.log(a);
}
expect_stdout: "PASS"
node_version: ">=8"
}
issue_4595: {
options = {
awaits: true,
inline: true,
}
input: {
(async function() {
await async function f() {
console.log(f.length);
}();
})();
}
expect: {
(async function() {
await async function f() {
console.log(f.length);
}();
})();
}
expect_stdout: "0"
node_version: ">=8"
}
issue_4598: {
options = {
conditionals: true,
}
input: {
if (console.log("PASS")) {
async function f() {}
}
}
expect: {
async function f() {}
console.log("PASS");
}
expect_stdout: "PASS"
node_version: ">=8"
}

62
test/compress/bigint.js Normal file
View File

@@ -0,0 +1,62 @@
arithmetic: {
input: {
console.log(((1n + 0x2n) * (0o3n - -4n)) >> (5n - 6n));
}
expect_exact: "console.log((1n+0x2n)*(0o3n- -4n)>>5n-6n);"
expect_stdout: "42n"
node_version: ">=10"
}
minus_dot: {
input: {
console.log(typeof -42n.toString(), typeof (-42n).toString());
}
expect_exact: "console.log(typeof-42n.toString(),typeof(-42n).toString());"
expect_stdout: "number string"
node_version: ">=10"
}
evaluate: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log((0xDEAD_BEEFn).toString(16));
}
expect: {
console.log(0xdeadbeefn.toString(16));
}
expect_stdout: "deadbeef"
node_version: ">=10"
}
Number: {
options = {
unsafe: true,
}
input: {
console.log(Number(-0xfeed_dead_beef_badn));
}
expect: {
console.log(+("" + -0xfeed_dead_beef_badn));
}
expect_stdout: "-1148098955808013200"
node_version: ">=10"
}
issue_4590: {
options = {
collapse_vars: true,
}
input: {
A = 1;
0n || console.log("PASS");
}
expect: {
A = 1;
0n || console.log("PASS");
}
expect_stdout: "PASS"
node_version: ">=10"
}

View File

@@ -5608,6 +5608,7 @@ collapse_rhs_array: {
collapse_rhs_boolean_1: {
options = {
collapse_vars: true,
evaluate: true,
}
input: {
var a, b;
@@ -5633,6 +5634,7 @@ collapse_rhs_boolean_1: {
collapse_rhs_boolean_2: {
options = {
collapse_vars: true,
evaluate: true,
}
input: {
var a;
@@ -5667,6 +5669,7 @@ collapse_rhs_boolean_3: {
booleans: true,
collapse_vars: true,
conditionals: true,
evaluate: true,
}
input: {
var a, f, g, h, i, n, s, t, x, y;
@@ -5720,6 +5723,7 @@ collapse_rhs_function: {
collapse_rhs_number: {
options = {
collapse_vars: true,
evaluate: true,
}
input: {
var a, b;
@@ -5799,6 +5803,7 @@ collapse_rhs_regexp: {
collapse_rhs_string: {
options = {
collapse_vars: true,
evaluate: true,
}
input: {
var a, b;
@@ -8705,3 +8710,48 @@ collapse_or_assign: {
}
expect_stdout: "PASS"
}
issue_4586_1: {
options = {
collapse_vars: true,
}
input: {
var a = 42;
(function f(b) {
var b = a;
if (b === arguments[0])
console.log("PASS");
})(console);
}
expect: {
var a = 42;
(function f(b) {
var b = a;
if (b === arguments[0])
console.log("PASS");
})(console);
}
expect_stdout: "PASS"
}
issue_4586_2: {
options = {
collapse_vars: true,
}
input: {
var a = 42;
(function f(b) {
b = a;
if (b === arguments[0])
console.log("PASS");
})(console);
}
expect: {
var a = 42;
(function f(b) {
if ((b = a) === arguments[0])
console.log("PASS");
})(console);
}
expect_stdout: "PASS"
}

View File

@@ -161,6 +161,7 @@ process_boolean_returns: {
collapse_value_1: {
options = {
collapse_vars: true,
keep_fargs: false,
unused: true,
}
input: {
@@ -169,7 +170,7 @@ collapse_value_1: {
}());
}
expect: {
console.log(function(a) {
console.log(function() {
return "PASS";
}());
}
@@ -180,6 +181,7 @@ collapse_value_1: {
collapse_value_2: {
options = {
collapse_vars: true,
keep_fargs: false,
unused: true,
}
input: {
@@ -188,7 +190,7 @@ collapse_value_2: {
})().log("PASS");
}
expect: {
(function(a) {
(function() {
return console;
})().log("PASS");
}
@@ -554,8 +556,9 @@ drop_fargs: {
"bar",
]
expect_warnings: [
"WARN: Dropping unused function argument c [test/compress/default-values.js:1,61]",
"WARN: Dropping unused default argument c [test/compress/default-values.js:1,61]",
"WARN: Side effects in default value of unused variable b [test/compress/default-values.js:1,37]",
"WARN: Dropping unused default argument assignment a [test/compress/default-values.js:1,29]",
]
node_version: ">=6"
}
@@ -1596,3 +1599,65 @@ issue_4548: {
]
node_version: ">=6"
}
issue_4588_1_unused: {
options = {
unused: true,
}
input: {
console.log(function(a = 42) {}.length);
}
expect: {
console.log(function(a = 0) {}.length);
}
expect_stdout: "0"
node_version: ">=6"
}
issue_4588_2_unused: {
options = {
unused: true,
}
input: {
console.log(function(a, b = void 0, c, d = "foo") {}.length);
}
expect: {
console.log(function(a, b = 0, c, d) {}.length);
}
expect_stdout: "1"
expect_warnings: [
"WARN: Dropping unused default argument assignment d [test/compress/default-values.js:1,47]",
"WARN: Dropping unused default argument value b [test/compress/default-values.js:1,32]",
]
node_version: ">=6"
}
issue_4588_1_evaluate: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log(function(a = 42) {}.length);
}
expect: {
console.log(0);
}
expect_stdout: "0"
node_version: ">=6"
}
issue_4588_2_evaluate: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log(function(a, b = void 0, c, d = "foo") {}.length);
}
expect: {
console.log(1);
}
expect_stdout: "1"
node_version: ">=6"
}

View File

@@ -2487,3 +2487,30 @@ issue_4554: {
expect_stdout: "PASS"
node_version: ">=6"
}
issue_4584: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
try {
(function f({
[console.log(a = "FAIL")]: a,
}) {})(0);
} catch (e) {
console.log("PASS");
}
}
expect: {
try {
(function f({
[console.log(a = "FAIL")]: a,
}) {})(0);
} catch (e) {
console.log("PASS");
}
}
expect_stdout: "PASS"
node_version: ">=6"
}

View File

@@ -2138,6 +2138,7 @@ issue_3497: {
issue_3515_1: {
options = {
collapse_vars: true,
evaluate: true,
reduce_vars: true,
unused: true,
}
@@ -2189,6 +2190,7 @@ issue_3515_2: {
issue_3515_3: {
options = {
collapse_vars: true,
evaluate: true,
unused: true,
}
input: {
@@ -2256,6 +2258,7 @@ function_assign: {
issue_3598: {
options = {
collapse_vars: true,
evaluate: true,
reduce_vars: true,
unused: true,
}

View File

@@ -0,0 +1,58 @@
precedence_1: {
input: {
console.log(-4 ** 3 ** 2);
}
expect_exact: "console.log((-4)**3**2);"
expect_stdout: "-262144"
node_version: ">=8"
}
precedence_2: {
input: {
console.log(-4 ** (3 ** 2));
}
expect_exact: "console.log((-4)**3**2);"
expect_stdout: "-262144"
node_version: ">=8"
}
precedence_3: {
input: {
console.log(-(4 ** 3) ** 2);
}
expect_exact: "console.log((-(4**3))**2);"
expect_stdout: "4096"
node_version: ">=8"
}
precedence_4: {
input: {
console.log((-4 ** 3) ** 2);
}
expect_exact: "console.log(((-4)**3)**2);"
expect_stdout: "4096"
node_version: ">=8"
}
await: {
input: {
(async a => a * await a ** ++a % a)(2).then(console.log);
}
expect_exact: "(async a=>a*(await a)**++a%a)(2).then(console.log);"
expect_stdout: "1"
node_version: ">=8"
}
evaluate: {
options = {
evaluate: true,
}
input: {
console.log(1 + 2 ** 3 - 4);
}
expect: {
console.log(5);
}
expect_stdout: "5"
node_version: ">=8"
}

View File

@@ -17,7 +17,7 @@ issue_269_1: {
expect: {
var x = {};
console.log(
x + "", +x, !!x,
"" + x, +("" + x), !!x,
"", 0, false
);
}

View File

@@ -338,7 +338,7 @@ evaluate_3: {
console.log(1 + Number(x) + 2);
}
expect: {
console.log(+x + 3);
console.log(+("" + x) + 3);
}
}

View File

@@ -979,6 +979,7 @@ collapse_vars_2_strict: {
collapse_rhs_true: {
options = {
collapse_vars: true,
evaluate: true,
pure_getters: true,
}
input: {
@@ -1015,6 +1016,7 @@ collapse_rhs_true: {
collapse_rhs_false: {
options = {
collapse_vars: true,
evaluate: true,
pure_getters: false,
}
input: {
@@ -1051,6 +1053,7 @@ collapse_rhs_false: {
collapse_rhs_strict: {
options = {
collapse_vars: true,
evaluate: true,
pure_getters: "strict",
}
input: {
@@ -1087,6 +1090,7 @@ collapse_rhs_strict: {
collapse_rhs_setter: {
options = {
collapse_vars: true,
evaluate: true,
pure_getters: "strict",
}
input: {

View File

@@ -663,3 +663,36 @@ issue_4562: {
expect_stdout: "f"
node_version: ">=6"
}
issue_4575: {
options = {
collapse_vars: true,
ie8: true,
reduce_vars: true,
rests: true,
unused: true,
}
input: {
A = "PASS";
(function() {
var a = 0, b = a;
var c = function a(...b) {
A;
var d = A;
console.log(d, b.length);
}();
})();
}
expect: {
A = "PASS";
(function() {
(function(b) {
A;
var d = A;
console.log(d, b.length);
})([]);
})();
}
expect_stdout: "PASS 0"
node_version: ">=6"
}

View File

@@ -13,6 +13,23 @@ console_log: {
]
}
console_log_console: {
input: {
var log = console.log;
log(console);
log(typeof console.log);
}
expect: {
var log = console.log;
log(console);
log(typeof console.log);
}
expect_stdout: [
"{ log: 'function(){}' }",
"function",
]
}
typeof_arguments: {
options = {
evaluate: true,
@@ -81,6 +98,63 @@ log_global: {
expect_stdout: "[object global]"
}
log_nested: {
options = {
unused: true,
}
input: {
var o = { p: 42 };
for (var i = 0; i < 10; i++)
o = {
p: o,
q: function foo() {},
};
console.log(o);
}
expect: {
var o = { p: 42 };
for (var i = 0; i < 10; i++)
o = {
p: o,
q: function() {},
};
console.log(o);
}
expect_stdout: true
}
timers: {
options = {
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var count = 0, interval = 1000, duration = 3210;
var timer = setInterval(function() {
console.log(++count);
}, interval);
setTimeout(function() {
clearInterval(timer);
}, duration);
}
expect: {
var count = 0;
var timer = setInterval(function() {
console.log(++count);
}, 1000);
setTimeout(function() {
clearInterval(timer);
}, 3210);
}
expect_stdout: [
"1",
"2",
"3",
]
node_version: ">=0.12"
}
issue_4054: {
input: {
console.log({

View File

@@ -56,7 +56,7 @@ describe("bin/uglifyjs", function() {
"--source-map", [
"names=true",
"url=inline",
].join(","),
].join(),
].join(" "), function(err, stdout) {
if (err) throw err;
var expected = [
@@ -84,7 +84,7 @@ describe("bin/uglifyjs", function() {
"--source-map", [
"names=false",
"url=inline",
].join(","),
].join(),
].join(" "), function(err, stdout) {
if (err) throw err;
var expected = [
@@ -171,7 +171,7 @@ describe("bin/uglifyjs", function() {
"content=" + mapFile,
"includeSources",
"url=inline",
].join(","),
].join(),
].join(" ");
var child = exec(command, function(err, stdout) {

View File

@@ -70,8 +70,8 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
} else if (differs.error) {
differs.warnings = warnings;
return differs;
} else if (is_error(differs.unminified_result)
&& is_error(differs.minified_result)
} else if (sandbox.is_error(differs.unminified_result)
&& sandbox.is_error(differs.minified_result)
&& differs.unminified_result.name == differs.minified_result.name) {
return {
code: [
@@ -558,8 +558,8 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
log(code);
log(diff.error.stack);
log("*** Discarding permutation and continuing.");
} else if (is_error(diff.unminified_result)
&& is_error(diff.minified_result)
} else if (sandbox.is_error(diff.unminified_result)
&& sandbox.is_error(diff.minified_result)
&& diff.unminified_result.name == diff.minified_result.name) {
// ignore difference in error messages caused by minification
diff_error_message = testcase;
@@ -600,10 +600,10 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
}
var lines = [ "" ];
if (isNaN(max_timeout)) {
lines.push("// minify error: " + to_comment(strip_color_codes(differs.minified_result.stack)));
lines.push("// minify error: " + to_comment(differs.minified_result.stack));
} else {
var unminified_result = strip_color_codes(differs.unminified_result);
var minified_result = strip_color_codes(differs.minified_result);
var unminified_result = differs.unminified_result;
var minified_result = differs.minified_result;
if (trim_trailing_whitespace(unminified_result) == trim_trailing_whitespace(minified_result)) {
lines.push(
"// (stringified)",
@@ -624,10 +624,6 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
}
};
function strip_color_codes(value) {
return ("" + value).replace(/\u001b\[\d+m/g, "");
}
function to_comment(value) {
return ("" + value).replace(/\n/g, "\n// ");
}
@@ -665,12 +661,8 @@ function has_loopcontrol(body, loop, label) {
return found;
}
function is_error(result) {
return result && typeof result.name == "string" && typeof result.message == "string";
}
function is_timed_out(result) {
return is_error(result) && /timed out/.test(result.message);
return sandbox.is_error(result) && /timed out/.test(result.message);
}
function is_statement(node) {

View File

@@ -1,103 +1,39 @@
var readFileSync = require("fs").readFileSync;
var semver = require("semver");
var spawnSync = require("child_process").spawnSync;
var vm = require("vm");
var setupContext = new vm.Script([
"[ Array, Boolean, Error, Function, Number, Object, RegExp, String ].forEach(function(f) {",
" f.toString = Function.prototype.toString;",
"});",
"Function.prototype.toString = function() {",
" var id = 100000;",
" return function() {",
" 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 n;",
" }",
" });",
] : [], [
" }",
' return "function(){}";',
" };",
"}();",
"this;",
]).join("\n"));
function createContext() {
var ctx = vm.createContext(Object.defineProperties({}, {
console: { value: { log: log } },
global: { get: self },
self: { get: self },
window: { get: self },
}));
var global = setupContext.runInContext(ctx);
return ctx;
function self() {
return this;
}
function safe_log(arg, level) {
if (arg) switch (typeof arg) {
case "function":
return arg.toString();
case "object":
if (arg === global) return "[object global]";
if (/Error$/.test(arg.name)) return arg.toString();
if (typeof arg.then == "function") return "[object Promise]";
arg.constructor.toString();
if (level--) for (var key in arg) {
var desc = Object.getOwnPropertyDescriptor(arg, key);
if (!desc || !desc.get && !desc.set) arg[key] = safe_log(arg[key], level);
}
}
return arg;
}
function log(msg) {
if (arguments.length == 1 && typeof msg == "string") return console.log("%s", msg);
return console.log.apply(console, [].map.call(arguments, function(arg) {
return safe_log(arg, 3);
}));
}
}
function run_code(code, toplevel, timeout) {
timeout = timeout || 5000;
var stdout = "";
var original_write = process.stdout.write;
process.stdout.write = function(chunk) {
stdout += chunk;
};
try {
vm.runInContext(toplevel ? "(function(){" + code + "})()" : code, createContext(), { timeout: timeout });
return stdout;
} catch (ex) {
return ex;
} finally {
process.stdout.write = original_write;
}
}
setup_log();
var setup_code = "(" + setup + ")(" + [
"this",
find_builtins(),
setup_log,
"function(process) {" + readFileSync(require.resolve("../tools/tty", "utf8")) + "}",
].join(",\n") + ");\n";
exports.has_toplevel = function(options) {
return options.toplevel
|| options.mangle && options.mangle.toplevel
|| options.compress && options.compress.toplevel;
};
exports.is_error = is_error;
exports.run_code = semver.satisfies(process.version, "0.8") ? function(code, toplevel, timeout) {
var stdout = run_code(code, toplevel, timeout);
var stdout = run_code_vm(code, toplevel, timeout);
if (typeof stdout != "string" || !/arguments/.test(code)) return stdout;
do {
var prev = stdout;
stdout = run_code(code, toplevel, timeout);
stdout = run_code_vm(code, toplevel, timeout);
} while (prev !== stdout);
return stdout;
} : run_code;
function strip_func_ids(text) {
return ("" + text).replace(/F[0-9]{6}N/g, "<F<>N>");
}
} : semver.satisfies(process.version, "<0.12") ? run_code_vm : function(code, toplevel, timeout) {
if (/\basync([ \t]+[^\s()[\]{},.&|!~=*%/+-]+|[ \t]*\([\s\S]*?\))[ \t]*=>|\b(async[ \t]+function|setInterval|setTimeout)\b/.test(code)) {
return run_code_exec(code, toplevel, timeout);
} else {
return run_code_vm(code, toplevel, timeout);
}
};
exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expected, actual) {
if (typeof expected != typeof actual) return false;
if (typeof expected == "object" && typeof expected.name == "string" && typeof expected.message == "string") {
if (is_error(expected)) {
if (expected.name !== actual.name) return false;
if (typeof actual.message != "string") return false;
expected = expected.message.slice(expected.message.lastIndexOf("\n") + 1);
@@ -107,8 +43,228 @@ exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expec
} : function(expected, actual) {
return typeof expected == typeof actual && strip_func_ids(expected) == strip_func_ids(actual);
};
exports.has_toplevel = function(options) {
return options.toplevel
|| options.mangle && options.mangle.toplevel
|| options.compress && options.compress.toplevel;
};
function is_error(result) {
return result && typeof result.name == "string" && typeof result.message == "string";
}
function strip_color_codes(value) {
return value.replace(/\u001b\[\d+m/g, "");
}
function strip_func_ids(text) {
return ("" + text).replace(/F[0-9]{6}N/g, "<F<>N>");
}
function setup_log() {
var inspect = require("util").inspect;
if (inspect.defaultOptions) {
var log_options = {
breakLength: Infinity,
colors: false,
compact: true,
customInspect: false,
depth: Infinity,
maxArrayLength: Infinity,
maxStringLength: Infinity,
showHidden: false,
};
for (var name in log_options) {
if (name in inspect.defaultOptions) inspect.defaultOptions[name] = log_options[name];
}
}
return inspect;
}
function find_builtins() {
setup_code = "console.log(Object.keys(this));";
var builtins = run_code_vm("");
if (semver.satisfies(process.version, ">=0.12")) builtins += ".concat(" + run_code_exec("") + ")";
return builtins;
}
function setup(global, builtins, setup_log, setup_tty) {
[ Array, Boolean, Error, Function, Number, Object, RegExp, String ].forEach(function(f) {
f.toString = Function.prototype.toString;
});
Function.prototype.toString = function() {
var configurable = Object.getOwnPropertyDescriptor(Function.prototype, "name").configurable;
var id = 100000;
return function() {
var n = this.name;
if (!/^F[0-9]{6}N$/.test(n)) {
n = "F" + ++id + "N";
if (configurable) Object.defineProperty(this, "name", {
get: function() {
return n;
}
});
}
return "function(){}";
};
}();
var process = global.process;
if (process) {
setup_tty(process);
var inspect = setup_log();
process.on("uncaughtException", function(ex) {
var value = ex;
if (value instanceof Error) {
value = {};
for (var name in ex) {
value[name] = ex[name];
delete ex[name];
}
}
process.stderr.write(inspect(value) + "\n\n-----===== UNCAUGHT EXCEPTION =====-----\n\n");
throw ex;
}).on("unhandledRejection", function() {});
}
var log = console.log;
var safe_console = {
log: function(msg) {
if (arguments.length == 1 && typeof msg == "string") return log("%s", msg);
return log.apply(null, [].map.call(arguments, function(arg) {
return safe_log(arg, {
level: 5,
original: [],
replaced: [],
});
}));
},
};
var props = {
// for Node.js v8
console: {
get: function() {
return safe_console;
},
},
global: { get: self },
self: { get: self },
window: { get: self },
};
[
// for Node.js v0.12
"Buffer",
"clearInterval",
"clearTimeout",
// for Node.js v0.12
"DTRACE_NET_STREAM_END",
// for Node.js v8
"process",
"setInterval",
"setTimeout",
].forEach(function(name) {
var value = global[name];
props[name] = {
get: function() {
return value;
},
};
});
builtins.forEach(function(name) {
try {
delete global[name];
} catch (e) {}
});
Object.defineProperties(global, props);
// for Node.js v8+
global.toString = function() {
return "[object global]";
};
function self() {
return this;
}
function safe_log(arg, cache) {
if (arg) switch (typeof arg) {
case "function":
return arg.toString();
case "object":
if (arg === global) return "[object global]";
if (/Error$/.test(arg.name)) return arg.toString();
if (typeof arg.then == "function") return "[object Promise]";
arg.constructor.toString();
var index = cache.original.indexOf(arg);
if (index >= 0) return cache.replaced[index];
if (--cache.level < 0) return "[object Object]";
var value = {};
cache.original.push(arg);
cache.replaced.push(value);
for (var key in arg) {
var desc = Object.getOwnPropertyDescriptor(arg, key);
if (desc && (desc.get || desc.set)) {
Object.defineProperty(value, key, desc);
} else {
value[key] = safe_log(arg[key], cache);
}
}
return value;
}
return arg;
}
}
function run_code_vm(code, toplevel, timeout) {
timeout = timeout || 5000;
var stdout = "";
var original_write = process.stdout.write;
process.stdout.write = function(chunk) {
stdout += chunk;
};
try {
var ctx = vm.createContext({ console: console });
// for Node.js v6
vm.runInContext(setup_code, ctx);
vm.runInContext(toplevel ? "(function(){" + code + "})();" : code, ctx, { timeout: timeout });
return strip_color_codes(stdout);
} catch (ex) {
return ex;
} finally {
process.stdout.write = original_write;
}
}
function run_code_exec(code, toplevel, timeout) {
if (toplevel) {
code = setup_code + "(function(){" + code + "})();";
} else {
code = code.replace(/^((["'])[^"']*\2(;|$))?/, function(directive) {
return directive + setup_code;
});
}
var result = spawnSync(process.argv[0], [ '--max-old-space-size=2048' ], {
encoding: "utf8",
input: code,
stdio: "pipe",
timeout: timeout || 5000,
});
if (result.status === 0) return result.stdout;
if (result.error && result.error.code == "ETIMEDOUT" || /FATAL ERROR:/.test(msg)) {
return new Error("Script execution timed out.");
}
if (result.error) return result.error;
var msg = result.stderr.replace(/\r\n/g, "\n");
var end = msg.indexOf("\n\n-----===== UNCAUGHT EXCEPTION =====-----\n\n");
var details;
if (end >= 0) {
details = msg.slice(0, end).replace(/<([1-9][0-9]*) empty items?>/g, function(match, count) {
return new Array(+count).join();
});
try {
details = vm.runInNewContext("(" + details + ")");
} catch (e) {}
}
var match = /\n([^:\s]*Error)(?:: ([\s\S]+?))?\n( at [\s\S]+)\n$/.exec(msg);
if (!match) return details;
var ex = new global[match[1]](match[2]);
ex.stack = ex.stack.slice(0, ex.stack.indexOf(" at ")) + match[3];
if (typeof details == "object") {
for (var name in details) ex[name] = details[name];
} else if (end >= 0) {
ex.details = details;
}
return ex;
}

View File

@@ -10,16 +10,17 @@ exports.init = function(url, auth, num) {
exports.should_stop = function(callback) {
read(base + "/actions/runs?per_page=100", function(reply) {
if (!reply || !Array.isArray(reply.workflow_runs)) return;
var runs = reply.workflow_runs.filter(function(workflow) {
return workflow.status != "completed";
}).sort(function(a, b) {
var runs = reply.workflow_runs.sort(function(a, b) {
return b.run_number - a.run_number;
});
var found = false, remaining = 20;
(function next() {
if (!runs.length) return;
var workflow = runs.pop();
if (workflow.event == "schedule" && workflow.run_number == run_number) found = true;
var workflow;
do {
workflow = runs.pop();
if (!workflow) return;
if (workflow.event == "schedule" && workflow.run_number == run_number) found = true;
} while (!found && workflow.status == "completed");
read(workflow.jobs_url, function(reply) {
if (!reply || !Array.isArray(reply.jobs)) return;
if (!reply.jobs.every(function(job) {

View File

@@ -134,11 +134,13 @@ var SUPPORT = function(matrix) {
}({
arrow: "a => 0;",
async: "async function f(){}",
bigint: "42n",
catch_omit_var: "try {} catch {}",
computed_key: "({[0]: 0});",
const_block: "var a; { const a = 0; }",
default_value: "[ a = 0 ] = [];",
destructuring: "[] = [];",
exponentiation: "0 ** 0",
let: "let a;",
rest: "var [...a] = [];",
rest_object: "var {...a} = {};",
@@ -167,7 +169,7 @@ var VALUES = [
"4",
"5",
"22",
"-0", // 0/-0 !== 0
"(-0)", // 0/-0 !== 0
"23..toString()",
"24 .toString()",
"25. ",
@@ -188,6 +190,12 @@ var VALUES = [
'"function"',
"this",
];
if (SUPPORT.bigint) VALUES = VALUES.concat([
"(!0o644n)",
"([3n][0] > 2)",
"(-42n).toString()",
"Number(0XDEADn << 16n | 0xbeefn)",
]);
var BINARY_OPS = [
" + ", // spaces needed to disambiguate with ++ cases (could otherwise cause syntax errors)
@@ -217,6 +225,7 @@ var BINARY_OPS = [
BINARY_OPS = BINARY_OPS.concat(BINARY_OPS);
BINARY_OPS = BINARY_OPS.concat(BINARY_OPS);
BINARY_OPS = BINARY_OPS.concat(BINARY_OPS);
if (SUPPORT.exponentiation) BINARY_OPS.push("**");
BINARY_OPS = BINARY_OPS.concat(BINARY_OPS);
BINARY_OPS = BINARY_OPS.concat(BINARY_OPS);
BINARY_OPS.push(" in ");
@@ -317,8 +326,6 @@ var VAR_NAMES = [
"NaN",
"Infinity",
"arguments",
"Math",
"parseInt",
"async",
"await",
];
@@ -433,6 +440,22 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was
unique_vars.length = len;
return pairs;
function mapShuffled(values, fn) {
var declare_only = [];
var side_effects = [];
values.forEach(function(value, index) {
value = fn(value, index);
if (/]:|=/.test(value) ? canThrow && rng(10) == 0 : rng(5)) {
declare_only.splice(rng(declare_only.length + 1), 0, value);
} else if (canThrow && rng(5) == 0) {
side_effects.splice(rng(side_effects.length + 1), 0, value);
} else {
side_effects.push(value);
}
});
return declare_only.concat(side_effects);
}
function convertToRest(names) {
var last = names.length - 1;
if (last >= 0 && SUPPORT.rest && rng(20) == 0) {
@@ -563,19 +586,24 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was
}
});
fill(function() {
var last = pairs.names.length - 1, has_rest = false;
var s = pairs.names.map(function(name, index) {
var last = pairs.names.length - 1, rest;
if (last >= 0 && !(last in keys) && SUPPORT.rest_object && rng(20) == 0) {
rest = pairs.names.pop();
if (!/=/.test(rest)) rest = "..." + rest;
}
var s = mapShuffled(pairs.names, function(name, index) {
if (index in keys) return keys[index] + ": " + name;
if (index == last && SUPPORT.rest_object && rng(20) == 0 && name.indexOf("=") < 0) {
has_rest = true;
return "..." + name;
}
return rng(10) == 0 ? name : createKey(recurmax, keys) + ": " + name;
}).join(", ");
if (!has_rest) s = addTrailingComma(s);
});
if (rest) {
s.push(rest);
s = s.join(", ");
} else {
s = addTrailingComma(s.join(", "));
}
names.unshift("{ " + s + " }" + createDefaultValue(recurmax, noDefault));
}, function() {
values.unshift("{ " + addTrailingComma(pairs.values.map(function(value, index) {
values.unshift("{ " + addTrailingComma(mapShuffled(pairs.values, function(value, index) {
var key = index in keys ? keys[index] : createKey(recurmax, keys);
return key + ": " + value;
}).join(", ")) + " }");
@@ -1020,9 +1048,9 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
case 0:
return [
"[ ",
new Array(rng(3)).join(","),
new Array(rng(3)).join(),
getVarName(NO_CONST),
new Array(rng(3)).join(","),
new Array(rng(3)).join(),
" ] = ",
createArrayLiteral(recurmax, stmtDepth, canThrow),
].join("");
@@ -1312,10 +1340,11 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) {
var save_async = async;
var s;
var name = createObjectKey(recurmax, stmtDepth, canThrow);
createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) {
switch (rng(SUPPORT.computed_key ? 3 : 2)) {
case 0:
async = false;
var fn;
switch (rng(SUPPORT.computed_key ? 3 : 2)) {
case 0:
async = false;
fn = function(defns) {
s = [
"get " + name + "(){",
strictMode(),
@@ -1324,13 +1353,15 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) {
createStatement(recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth, STMT_RETURN_ETC),
"},",
];
break;
case 1:
var prop;
do {
prop = getDotKey();
} while (name == prop);
async = false;
};
break;
case 1:
var prop;
do {
prop = getDotKey();
} while (name == prop);
async = false;
fn = function(defns) {
s = [
"set " + name + "(" + createVarName(MANDATORY) + "){",
strictMode(),
@@ -1339,9 +1370,11 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) {
"this." + prop + createAssignment() + _createBinaryExpr(recurmax, COMMA_OK, stmtDepth, canThrow) + ";",
"},",
];
break;
default:
async = SUPPORT.async && rng(50) == 0;
};
break;
default:
async = SUPPORT.async && rng(50) == 0;
fn = function(defns) {
s = [
(async ? "async " : "") + name + "(" + createParams(save_async, NO_DUPLICATE) + "){",
strictMode(),
@@ -1349,9 +1382,10 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) {
_createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
"},",
]
break;
}
});
};
break;
}
createBlockVariables(recurmax, stmtDepth, canThrow, fn);
async = save_async;
VAR_NAMES.length = nameLenBefore;
return filterDirective(s).join("\n");
@@ -1557,9 +1591,10 @@ function getVarName(noConst) {
function createVarName(maybe, dontStore) {
if (!maybe || rng(2)) {
var suffix = rng(3);
var name;
var name, tries = 10;
do {
name = VAR_NAMES[rng(VAR_NAMES.length)];
if (--tries < 0) suffix++;
if (suffix) name += "_" + suffix;
} while (unique_vars.indexOf(name) >= 0 || block_vars.indexOf(name) >= 0 || async && name == "await");
if (!dontStore) VAR_NAMES.push(name);
@@ -1743,24 +1778,52 @@ function log(options) {
}
function sort_globals(code) {
var globals = sandbox.run_code("throw Object.keys(this).sort();" + code);
var globals = sandbox.run_code("throw Object.keys(this).sort(" + function(global) {
return function(m, n) {
return (n == "toString") - (m == "toString")
|| (typeof global[n] == "function") - (typeof global[m] == "function")
|| (m < n ? -1 : m > n ? 1 : 0);
};
} + "(this));" + code);
if (!Array.isArray(globals)) {
errorln();
errorln();
errorln("//-------------------------------------------------------------");
errorln("// !!! sort_globals() failed !!!");
errorln("// expected Array, got:");
if (!sandbox.is_error(globals)) try {
globals = JSON.stringify(globals);
} catch (e) {}
errorln(globals);
errorln("//");
errorln(code);
errorln();
return code;
}
return globals.length ? "var " + globals.map(function(name) {
return name + "=" + name;
}).join(",") + ";" + code : code;
}).join() + ";" + code : code;
}
function fuzzy_match(original, uglified) {
uglified = uglified.split(" ");
var i = uglified.length;
original = original.split(" ", i);
while (--i >= 0) {
if (original[i] === uglified[i]) continue;
var a = +original[i];
var b = +uglified[i];
if (Math.abs((b - a) / a) < 1e-10) continue;
return false;
var m = [], n = [];
if (collect(original, m) !== collect(uglified, n)) return false;
for (var i = 0; i < m.length; i++) {
var a = m[i];
var b = n[i];
if (Math.abs((b - a) / a) > 1e-10) return false;
}
return true;
function collect(input, nums) {
return input.replace(/-?([1-9][0-9]*(\.[0-9]+)?|0\.[0-9]+)(e-?[1-9][0-9]*)?/ig, function(num) {
return "<|" + nums.push(+num) + "|>";
});
}
}
function is_error_timeout(ex) {
return /timed out/.test(ex.message);
}
function is_error_in(ex) {
@@ -1768,13 +1831,17 @@ function is_error_in(ex) {
}
function is_error_spread(ex) {
return ex.name == "TypeError" && /Found non-callable @@iterator|is not iterable|undefined is not a function/.test(ex.message);
return ex.name == "TypeError" && /Found non-callable @@iterator| is not iterable| is not a function/.test(ex.message);
}
function is_error_recursion(ex) {
return ex.name == "RangeError" && /Invalid string length|Maximum call stack size exceeded/.test(ex.message);
}
function is_error_destructuring(ex) {
return ex.name == "TypeError" && /^Cannot destructure /.test(ex.message);
}
function patch_try_catch(orig, toplevel) {
var stack = [ {
code: orig,
@@ -1782,6 +1849,7 @@ function patch_try_catch(orig, toplevel) {
offset: 0,
tries: [],
} ];
var tail_throw = '\nif (typeof UFUZZ_ERROR == "object") throw UFUZZ_ERROR;\n';
var re = /(?:(?:^|[\s{}):;])try|}\s*catch\s*\(([^)[{]+)\)|}\s*finally)\s*(?={)/g;
while (stack.length) {
var code = stack[0].code;
@@ -1798,7 +1866,7 @@ function patch_try_catch(orig, toplevel) {
var insert;
if (/}\s*finally\s*$/.test(match[0])) {
tries.shift();
insert = 'if (typeof UFUZZ_ERROR == "object") throw UFUZZ_ERROR;';
insert = tail_throw;
} else {
while (tries.length && tries[0].catch) tries.shift();
tries[0].catch = index - offset;
@@ -1812,9 +1880,9 @@ function patch_try_catch(orig, toplevel) {
"throw " + match[1] + ";",
].join("\n");
}
var new_code = code.slice(0, index) + insert + code.slice(index);
var new_code = code.slice(0, index) + insert + code.slice(index) + tail_throw;
var result = sandbox.run_code(new_code, toplevel);
if (typeof result != "object" || typeof result.name != "string" || typeof result.message != "string") {
if (!sandbox.is_error(result)) {
if (!stack.filled && match[1]) stack.push({
code: code,
index: index && index - 1,
@@ -1832,6 +1900,9 @@ function patch_try_catch(orig, toplevel) {
} else if (is_error_recursion(result)) {
index = result.ufuzz_try;
return orig.slice(0, index) + 'throw new Error("skipping infinite recursion");' + orig.slice(index);
} else if (is_error_destructuring(result)) {
index = result.ufuzz_catch;
return orig.slice(0, index) + result.ufuzz_var + ' = new Error("cannot destructure");' + orig.slice(index);
}
}
stack.filled = true;
@@ -1887,6 +1958,7 @@ for (var round = 1; round <= num_iterations; round++) {
println("original result:");
println(orig_result[0]);
println();
// ignore v8 parser bug
if (is_bug_async_arrow_rest(orig_result[0])) continue;
}
minify_options.forEach(function(options) {
@@ -1899,13 +1971,19 @@ for (var round = 1; round <= num_iterations; round++) {
uglify_code = uglify_code.code;
uglify_result = sandbox.run_code(uglify_code, toplevel);
ok = sandbox.same_stdout(original_result, uglify_result);
// ignore v8 parser bug
if (!ok && is_bug_async_arrow_rest(uglify_result)) ok = true;
// ignore declaration order of global variables
if (!ok && !toplevel) {
ok = sandbox.same_stdout(sandbox.run_code(sort_globals(original_code)), sandbox.run_code(sort_globals(uglify_code)));
}
// ignore numerical imprecision caused by `unsafe_math`
if (!ok && o.compress && o.compress.unsafe_math && typeof original_result == "string" && typeof uglify_result == "string") {
ok = fuzzy_match(original_result, uglify_result);
if (!ok && o.compress && o.compress.unsafe_math && typeof original_result == typeof uglify_result) {
if (typeof original_result == "string") {
ok = fuzzy_match(original_result, uglify_result);
} else if (sandbox.is_error(original_result)) {
ok = original_result.name == uglify_result.name && fuzzy_match(original_result.message, uglify_result.message);
}
if (!ok) {
var fuzzy_result = sandbox.run_code(original_code.replace(/( - 0\.1){3}/g, " - 0.3"), toplevel);
ok = sandbox.same_stdout(fuzzy_result, uglify_result);
@@ -1913,10 +1991,15 @@ for (var round = 1; round <= num_iterations; round++) {
}
// ignore difference in error message caused by Temporal Dead Zone
if (!ok && errored && uglify_result.name == "ReferenceError" && original_result.name == "ReferenceError") ok = true;
// ignore spurious time-outs
if (!ok && errored && /timed out/.test(original_result.message) && !/timed out/.test(uglify_result.message)) {
if (!orig_result[toplevel ? 3 : 2]) orig_result[toplevel ? 3 : 2] = sandbox.run_code(original_code, toplevel, 10000);
ok = sandbox.same_stdout(orig_result[toplevel ? 3 : 2], uglify_result);
if (!ok && errored && is_error_timeout(original_result)) {
if (is_error_timeout(uglify_result)) {
// ignore difference in error message
ok = true;
} else {
// ignore spurious time-outs
if (!orig_result[toplevel ? 3 : 2]) orig_result[toplevel ? 3 : 2] = sandbox.run_code(original_code, toplevel, 10000);
ok = sandbox.same_stdout(orig_result[toplevel ? 3 : 2], uglify_result);
}
}
// ignore difference in error message caused by `in`
if (!ok && errored && is_error_in(uglify_result) && is_error_in(original_result)) ok = true;
@@ -1926,6 +2009,10 @@ for (var round = 1; round <= num_iterations; round++) {
if (!ok && errored && is_error_recursion(original_result)) {
if (is_error_recursion(uglify_result) || typeof uglify_result == "string") ok = true;
}
// ignore difference in error message caused by destructuring
if (!ok && errored && is_error_destructuring(uglify_result) && is_error_destructuring(original_result)) {
ok = true;
}
// ignore errors above when caught by try-catch
if (!ok) {
var orig_skipped = patch_try_catch(original_code, toplevel);