From: Alex Lam S.L Date: Sun, 11 Oct 2020 17:18:57 +0000 (+0100) Subject: support `const` (#4190) X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=55451e7b78c1765c6c3011d880c7980c10b7330f;p=UglifyJS.git support `const` (#4190) --- diff --git a/lib/ast.js b/lib/ast.js index ac779cb9..5ce591cc 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -203,6 +203,10 @@ var AST_Directive = DEFNODE("Directive", "value quote", { }, }, AST_Statement); +var AST_EmptyStatement = DEFNODE("EmptyStatement", null, { + $documentation: "The empty statement (empty block or simply a semicolon)" +}, AST_Statement); + function must_be_expression(node, prop) { if (!(node[prop] instanceof AST_Node)) throw new Error(prop + " must be AST_Node"); if (node[prop] instanceof AST_Statement && !(node[prop] instanceof AST_Function)) { @@ -226,35 +230,9 @@ var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", { }, }, AST_Statement); -function walk_body(node, visitor) { - node.body.forEach(function(node) { - node.walk(visitor); - }); -} - -var AST_Block = DEFNODE("Block", "body", { - $documentation: "A body of statements (usually braced)", - $propdoc: { - body: "[AST_Statement*] an array of statements" - }, - walk: function(visitor) { - var node = this; - visitor.visit(node, function() { - walk_body(node, visitor); - }); - }, - _validate: function() { - this.body.forEach(function(node) { - if (!(node instanceof AST_Statement)) throw new Error("body must be AST_Statement[]"); - if (node instanceof AST_Function) throw new Error("body cannot contain AST_Function"); - }); - }, -}, AST_Statement); - -var AST_BlockScope = DEFNODE("BlockScope", "cname enclosed functions make_def parent_scope variables", { +var AST_BlockScope = DEFNODE("BlockScope", "enclosed functions make_def parent_scope variables", { $documentation: "Base class for all statements introducing a lexical scope", $propdoc: { - cname: "[integer/S] current index for mangling variables (used internally by the mangler)", 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", @@ -278,15 +256,36 @@ var AST_BlockScope = DEFNODE("BlockScope", "cname enclosed functions make_def pa if (!(this.parent_scope instanceof AST_BlockScope)) throw new Error("parent_scope must be AST_BlockScope"); if (!(this.resolve() instanceof AST_Scope)) throw new Error("must be contained within AST_Scope"); }, -}, AST_Block); +}, AST_Statement); -var AST_BlockStatement = DEFNODE("BlockStatement", null, { - $documentation: "A block statement", +function walk_body(node, visitor) { + node.body.forEach(function(node) { + node.walk(visitor); + }); +} + +var AST_Block = DEFNODE("Block", "body", { + $documentation: "A body of statements (usually braced)", + $propdoc: { + body: "[AST_Statement*] an array of statements" + }, + walk: function(visitor) { + var node = this; + visitor.visit(node, function() { + walk_body(node, visitor); + }); + }, + _validate: function() { + this.body.forEach(function(node) { + if (!(node instanceof AST_Statement)) throw new Error("body must be AST_Statement[]"); + if (node instanceof AST_Function) throw new Error("body cannot contain AST_Function"); + }); + }, }, AST_BlockScope); -var AST_EmptyStatement = DEFNODE("EmptyStatement", null, { - $documentation: "The empty statement (empty block or simply a semicolon)" -}, AST_Statement); +var AST_BlockStatement = DEFNODE("BlockStatement", null, { + $documentation: "A block statement", +}, AST_Block); var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", { $documentation: "Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`", @@ -297,7 +296,7 @@ var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", { if (!(this.body instanceof AST_Statement)) throw new Error("body must be AST_Statement"); if (this.body instanceof AST_Function) throw new Error("body cannot be AST_Function"); }, -}, AST_Statement); +}, AST_BlockScope); var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", { $documentation: "Statement with a label", @@ -451,7 +450,7 @@ var AST_Scope = DEFNODE("Scope", "uses_eval uses_with", { return this.uses_eval || this.uses_with; }, resolve: return_this, -}, AST_BlockScope); +}, AST_Block); var AST_Toplevel = DEFNODE("Toplevel", "globals", { $documentation: "The toplevel scope", @@ -699,7 +698,7 @@ var AST_Try = DEFNODE("Try", "bcatch bfinally", { if (!(this.bfinally instanceof AST_Finally)) throw new Error("bfinally must be AST_Finally"); } }, -}, AST_BlockScope); +}, AST_Block); var AST_Catch = DEFNODE("Catch", "argname", { $documentation: "A `catch` node; only makes sense as part of a `try` statement", @@ -718,11 +717,11 @@ var AST_Catch = DEFNODE("Catch", "argname", { if (!(this.argname instanceof AST_SymbolCatch)) throw new Error("argname must be AST_SymbolCatch"); } }, -}, AST_BlockScope); +}, AST_Block); var AST_Finally = DEFNODE("Finally", null, { $documentation: "A `finally` node; only makes sense as part of a `try` statement" -}, AST_BlockScope); +}, AST_Block); /* -----[ VAR ]----- */ @@ -738,14 +737,30 @@ var AST_Definitions = DEFNODE("Definitions", "definitions", { defn.walk(visitor); }); }); - } + }, + _validate: function() { + if (this.definitions.length < 1) throw new Error("must have at least one definition"); + }, }, AST_Statement); +var AST_Const = DEFNODE("Const", null, { + $documentation: "A `const` statement", + _validate: function() { + this.definitions.forEach(function(node) { + if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]"); + if (!(node.name instanceof AST_SymbolConst)) throw new Error("name must be AST_SymbolConst"); + must_be_expression(node, "value"); + }); + }, +}, AST_Definitions); + var AST_Var = DEFNODE("Var", null, { $documentation: "A `var` statement", _validate: function() { this.definitions.forEach(function(node) { if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]"); + if (!(node.name instanceof AST_SymbolVar)) throw new Error("name must be AST_SymbolVar"); + if (node.value != null) must_be_expression(node, "value"); }); }, }, AST_Definitions); @@ -763,10 +778,6 @@ var AST_VarDef = DEFNODE("VarDef", "name value", { if (node.value) node.value.walk(visitor); }); }, - _validate: function() { - if (!(this.name instanceof AST_SymbolVar)) throw new Error("name must be AST_SymbolVar"); - if (this.value != null) must_be_expression(this, "value"); - }, }); /* -----[ OTHER ]----- */ @@ -1032,12 +1043,12 @@ var AST_ObjectGetter = DEFNODE("ObjectGetter", null, { }, AST_ObjectProperty); var AST_Symbol = DEFNODE("Symbol", "scope name thedef", { + $documentation: "Base class for all symbols", $propdoc: { name: "[string] name of this symbol", scope: "[AST_Scope/S] the current scope (not necessarily the definition scope)", thedef: "[SymbolDef/S] the definition of this symbol" }, - $documentation: "Base class for all symbols", _validate: function() { if (typeof this.name != "string") throw new Error("name must be string"); }, @@ -1051,6 +1062,10 @@ var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", { $documentation: "A declaration symbol (symbol in var, function name or argument, symbol in catch)", }, AST_Symbol); +var AST_SymbolConst = DEFNODE("SymbolConst", null, { + $documentation: "Symbol defining a constant", +}, AST_SymbolDeclaration); + var AST_SymbolVar = DEFNODE("SymbolVar", null, { $documentation: "Symbol defining a variable", }, AST_SymbolDeclaration); diff --git a/lib/compress.js b/lib/compress.js index fa1cc383..04932374 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -370,7 +370,8 @@ merge(Compressor.prototype, { def.cross_loop = false; def.direct_access = false; def.escaped = []; - def.fixed = !def.scope.pinned() + def.fixed = !def.const_redefs + && !def.scope.pinned() && !compressor.exposed(def) && !(def.init instanceof AST_Function && def.init !== def.scope) && def.init; @@ -481,8 +482,10 @@ merge(Compressor.prototype, { return def.fixed instanceof AST_Defun; } - function safe_to_assign(tw, def) { - if (def.fixed === undefined) return true; + function safe_to_assign(tw, def, declare) { + if (def.fixed === undefined) return declare || all(def.orig, function(sym) { + return !(sym instanceof AST_SymbolConst); + }); if (def.fixed === null && def.safe_ids) { def.safe_ids[def.id] = false; delete def.safe_ids; @@ -654,6 +657,11 @@ merge(Compressor.prototype, { pop(tw); return true; }); + def(AST_BlockScope, function(tw, descend, compressor) { + this.variables.each(function(def) { + reset_def(tw, compressor, def); + }); + }); def(AST_Call, function(tw, descend) { tw.find_parent(AST_Scope).may_call_this(); var exp = this.expression; @@ -717,7 +725,10 @@ merge(Compressor.prototype, { tw.in_loop = saved_loop; return true; }); - def(AST_For, function(tw) { + def(AST_For, function(tw, descend, compressor) { + this.variables.each(function(def) { + reset_def(tw, compressor, def); + }); if (this.init) this.init.walk(tw); var saved_loop = tw.in_loop; tw.in_loop = this; @@ -735,7 +746,10 @@ merge(Compressor.prototype, { tw.in_loop = saved_loop; return true; }); - def(AST_ForIn, function(tw) { + def(AST_ForIn, function(tw, descend, compressor) { + this.variables.each(function(def) { + reset_def(tw, compressor, def); + }); this.object.walk(tw); var saved_loop = tw.in_loop; tw.in_loop = this; @@ -816,7 +830,10 @@ merge(Compressor.prototype, { pop(tw); return true; }); - def(AST_Switch, function(tw) { + def(AST_Switch, function(tw, descend, compressor) { + this.variables.each(function(def) { + reset_def(tw, compressor, def); + }); this.expression.walk(tw); var first = true; this.body.forEach(function(branch) { @@ -900,7 +917,10 @@ merge(Compressor.prototype, { walk_defuns(tw, this); return true; }); - def(AST_Try, function(tw) { + def(AST_Try, function(tw, descend, compressor) { + this.variables.each(function(def) { + reset_def(tw, compressor, def); + }); push(tw); walk_body(this, tw); pop(tw); @@ -963,7 +983,7 @@ merge(Compressor.prototype, { if (!node.value) return; descend(); var d = node.name.definition(); - if (safe_to_assign(tw, d)) { + if (safe_to_assign(tw, d, true)) { mark(tw, d); tw.loop_ids[d.id] = tw.in_loop; d.fixed = function() { @@ -1667,8 +1687,6 @@ merge(Compressor.prototype, { extract_candidates(expr.condition); extract_candidates(expr.consequent); extract_candidates(expr.alternative); - } else if (expr instanceof AST_Definitions) { - expr.definitions.forEach(extract_candidates); } else if (expr instanceof AST_Dot) { extract_candidates(expr.expression); } else if (expr instanceof AST_DWLoop) { @@ -1722,6 +1740,8 @@ merge(Compressor.prototype, { } else { extract_candidates(expr.expression); } + } else if (expr instanceof AST_Var) { + expr.definitions.forEach(extract_candidates); } else if (expr instanceof AST_VarDef) { if (expr.value) { var def = expr.name.definition(); @@ -1895,6 +1915,7 @@ merge(Compressor.prototype, { function get_lhs(expr) { if (expr instanceof AST_VarDef) { var def = expr.name.definition(); + if (def.const_redefs) return; if (!member(expr.name, def.orig)) return; var declared = def.orig.length - def.eliminated - (declare_only[def.name] || 0); var referenced = def.references.length - def.replaced - (assignments[def.name] || 0); @@ -2139,11 +2160,15 @@ merge(Compressor.prototype, { for (var i = 0; i < statements.length;) { var stat = statements[i]; if (stat instanceof AST_BlockStatement) { - CHANGED = true; - eliminate_spurious_blocks(stat.body); - [].splice.apply(statements, [i, 1].concat(stat.body)); - i += stat.body.length; - continue; + if (all(stat.body, function(stat) { + return !(stat instanceof AST_Const); + })) { + CHANGED = true; + eliminate_spurious_blocks(stat.body); + [].splice.apply(statements, [i, 1].concat(stat.body)); + i += stat.body.length; + continue; + } } if (stat instanceof AST_Directive) { if (member(stat.value, seen_dirs)) { @@ -2379,12 +2404,15 @@ merge(Compressor.prototype, { } function as_statement_array_with_return(node, ab) { - var body = as_statement_array(node).slice(0, -1); - if (ab.value) { - body.push(make_node(AST_SimpleStatement, ab.value, { - body: ab.value.expression - })); - } + var body = as_statement_array(node); + var block = body, last; + while ((last = block[block.length - 1]) !== ab) { + block = last.body; + } + block.pop(); + if (ab.value) body.push(make_node(AST_SimpleStatement, ab.value, { + body: ab.value.expression + })); return body; } @@ -2430,7 +2458,7 @@ merge(Compressor.prototype, { statements.length = n; CHANGED = n != len; if (has_quit) has_quit.forEach(function(stat) { - extract_declarations_from_unreachable_code(stat, statements); + extract_declarations_from_unreachable_code(compressor, stat, statements); }); } @@ -2574,7 +2602,7 @@ merge(Compressor.prototype, { if (merge_conditional_assignments(def, exprs, keep)) trimmed = true; break; } - if (join_var_assign(defn.definitions, exprs, keep)) trimmed = true; + if (defn instanceof AST_Var && join_var_assign(defn.definitions, exprs, keep)) trimmed = true; } return trimmed && exprs; } @@ -2668,7 +2696,7 @@ merge(Compressor.prototype, { CHANGED = true; } else { statements[++j] = stat; - defs = stat; + if (stat instanceof AST_Var) defs = stat; } continue; } else if (stat instanceof AST_Exit) { @@ -2690,7 +2718,7 @@ merge(Compressor.prototype, { defs.definitions = defs.definitions.concat(stat.init.definitions); stat.init = null; CHANGED = true; - } else if (stat.init instanceof AST_Definitions) { + } else if (stat.init instanceof AST_Var) { defs = stat.init; } } else if (stat instanceof AST_ForIn) { @@ -2750,25 +2778,46 @@ merge(Compressor.prototype, { } } - function extract_declarations_from_unreachable_code(stat, target) { + function extract_declarations_from_unreachable_code(compressor, stat, target) { if (!(stat instanceof AST_Defun)) { AST_Node.warn("Dropping unreachable code [{file}:{line},{col}]", stat.start); } - stat.walk(new TreeWalker(function(node) { + var block; + stat.walk(new TreeWalker(function(node, descend) { if (node instanceof AST_Definitions) { AST_Node.warn("Declarations in unreachable code! [{file}:{line},{col}]", node.start); - node.remove_initializers(); - target.push(node); + node.remove_initializers(compressor); + push(node); return true; } if (node instanceof AST_Defun) { - target.push(node); + push(node); return true; } - if (node instanceof AST_Scope) { + if (node instanceof AST_Scope) return true; + if (node instanceof AST_BlockScope) { + var save = block; + block = []; + descend(); + if (block.required) { + target.push(make_node(AST_BlockStatement, stat, { + body: block + })); + } else if (block.length) { + [].push.apply(target, block); + } + block = save; return true; } })); + function push(node) { + if (block) { + block.push(node); + if (node instanceof AST_Const) block.required = true; + } else { + target.push(node); + } + } } function is_undefined(node, compressor) { @@ -4038,7 +4087,9 @@ merge(Compressor.prototype, { }); def(AST_SymbolDeclaration, return_false); def(AST_SymbolRef, function(compressor) { - return !this.is_declared(compressor); + return !(this.is_declared(compressor) && all(this.definition().orig, function(sym) { + return !(sym instanceof AST_SymbolConst); + })); }); def(AST_This, return_false); def(AST_Try, function(compressor) { @@ -4285,12 +4336,15 @@ merge(Compressor.prototype, { return self; }); - function trim_block(stat) { - switch (stat.body.length) { - case 1: return stat.body[0]; - case 0: return make_node(AST_EmptyStatement, stat); + function trim_block(node) { + switch (node.body.length) { + case 0: + return make_node(AST_EmptyStatement, node); + case 1: + var stat = node.body[0]; + if (!(stat instanceof AST_Const)) return stat; } - return stat; + return node; } OPT(AST_BlockStatement, function(self, compressor) { @@ -4386,6 +4440,16 @@ merge(Compressor.prototype, { pop(); return true; } + if (node instanceof AST_Const) { + node.definitions.forEach(function(defn) { + var def = defn.name.definition(); + references[def.id] = false; + def = def.redefined(); + if (def) references[def.id] = false; + defn.value.walk(tw); + }); + return true; + } if (node instanceof AST_For) { if (node.init) node.init.walk(tw); push(); @@ -4633,7 +4697,7 @@ merge(Compressor.prototype, { if (!(sym instanceof AST_SymbolRef)) return; if (compressor.exposed(sym.definition())) return; if (!all(sym.definition().orig, function(sym) { - return !(sym instanceof AST_SymbolLambda); + return !(sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLambda); })) return; return sym; }; @@ -4668,42 +4732,40 @@ merge(Compressor.prototype, { }); } if (node === self) return; - if (node instanceof AST_Defun) { - var node_def = node.name.definition(); - if (!drop_funcs && scope === self) { - if (!(node_def.id in in_use_ids)) { - in_use_ids[node_def.id] = true; - in_use.push(node_def); + if (scope === self) { + if (node instanceof AST_Defun) { + var def = node.name.definition(); + if (!drop_funcs && !(def.id in in_use_ids)) { + in_use_ids[def.id] = true; + in_use.push(def); } + initializations.add(def.id, node); + return true; // don't go in nested scopes } - initializations.add(node_def.id, node); - return true; // don't go in nested scopes - } - if (node instanceof AST_SymbolFunarg && scope === self) { - var node_def = node.definition(); - var_defs_by_id.add(node_def.id, node); - assignments.add(node_def.id, node); - } - if (node instanceof AST_Definitions && scope === self) { - node.definitions.forEach(function(def) { - var node_def = def.name.definition(); - var_defs_by_id.add(node_def.id, def); - if (!drop_vars) { - if (!(node_def.id in in_use_ids)) { - in_use_ids[node_def.id] = true; - in_use.push(node_def); + if (node instanceof AST_Definitions) { + node.definitions.forEach(function(defn) { + var def = defn.name.definition(); + var_defs_by_id.add(def.id, defn); + if (!drop_vars && !(def.id in in_use_ids)) { + in_use_ids[def.id] = true; + in_use.push(def); } - } - if (def.value) { - if (def.value.has_side_effects(compressor)) { - def.value.walk(tw); + if (!defn.value) return; + if (defn.value.has_side_effects(compressor)) { + defn.value.walk(tw); } else { - initializations.add(node_def.id, def.value); + initializations.add(def.id, defn.value); } - assignments.add(node_def.id, def); - } - }); - return true; + assignments.add(def.id, defn); + }); + return true; + } + if (node instanceof AST_SymbolFunarg) { + var def = node.definition(); + var_defs_by_id.add(def.id, node); + assignments.add(def.id, node); + return true; + } } return scan_ref_scoped(node, descend, true); }); @@ -4714,7 +4776,14 @@ merge(Compressor.prototype, { // symbols (that may not be in_use). tw = new TreeWalker(scan_ref_scoped); for (var i = 0; i < in_use.length; i++) { - var init = initializations.get(in_use[i].id); + var in_use_def = in_use[i]; + if (in_use_def.const_redefs) in_use_def.const_redefs.forEach(function(def) { + if (!(def.id in in_use_ids)) { + in_use_ids[def.id] = true; + in_use.push(def); + } + }); + var init = initializations.get(in_use_def.id); if (init) init.forEach(function(init) { init.walk(tw); }); @@ -4915,7 +4984,8 @@ merge(Compressor.prototype, { } tail.push(def); } - } else if (sym.orig[0] instanceof AST_SymbolCatch) { + } else if (sym.orig[0] instanceof AST_SymbolCatch + && sym.scope.resolve() === def.name.scope.resolve()) { var value = def.value && def.value.drop_side_effect_free(compressor); if (value) side_effects.push(value); var var_defs = var_defs_by_id.get(sym.id); @@ -5012,7 +5082,13 @@ merge(Compressor.prototype, { return node; } }, function(node, in_list) { - if (node instanceof AST_For) { + 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)) return stat; + } else if (node instanceof AST_For) { // Certain combination of unused name + side effect leads to invalid AST: // https://github.com/mishoo/UglifyJS/issues/44 // https://github.com/mishoo/UglifyJS/issues/1838 @@ -5574,6 +5650,9 @@ merge(Compressor.prototype, { }) && all(def.references, function(ref) { return ref.fixed_value() === right; + }) + && all(def.orig, function(sym) { + return !(sym instanceof AST_SymbolConst); }); } }); @@ -5790,7 +5869,9 @@ merge(Compressor.prototype, { return make_sequence(this, [ expression, property ]); }); def(AST_SymbolRef, function(compressor) { - return this.is_declared(compressor) ? null : this; + return this.is_declared(compressor) && all(this.definition().orig, function(sym) { + return !(sym instanceof AST_SymbolConst); + }) ? null : this; }); def(AST_This, return_null); def(AST_Unary, function(compressor, first_in_statement) { @@ -5848,26 +5929,24 @@ merge(Compressor.prototype, { if (!compressor.option("loops")) return self; var cond = self.condition.is_truthy() || self.condition.evaluate(compressor, true); if (!(cond instanceof AST_Node)) { - if (cond) return make_node(AST_For, self, { + if (cond && !has_loop_control(self, compressor.parent(), AST_Continue)) return make_node(AST_For, self, { body: make_node(AST_BlockStatement, self.body, { body: [ self.body, make_node(AST_SimpleStatement, self.condition, { body: self.condition - }) + }), ] }) }).optimize(compressor); - if (!has_loop_control(self, compressor.parent())) { - return make_node(AST_BlockStatement, self.body, { - body: [ - self.body, - make_node(AST_SimpleStatement, self.condition, { - body: self.condition - }) - ] - }).optimize(compressor); - } + if (!has_loop_control(self, compressor.parent())) return make_node(AST_BlockStatement, self.body, { + body: [ + self.body, + make_node(AST_SimpleStatement, self.condition, { + body: self.condition + }), + ] + }).optimize(compressor); } if (self.body instanceof AST_BlockStatement && !has_loop_control(self, compressor.parent(), AST_Continue)) { var body = self.body.body; @@ -5877,6 +5956,7 @@ merge(Compressor.prototype, { && !stat.alternative && stat.body instanceof AST_Break && compressor.loopcontrol_target(stat.body) === self) { + if (has_block_scope_refs(stat.condition)) break; self.condition = make_node(AST_Binary, self, { operator: "&&", left: stat.condition.negate(compressor), @@ -5884,6 +5964,7 @@ merge(Compressor.prototype, { }); body.splice(i, 1); } else if (stat instanceof AST_SimpleStatement) { + if (has_block_scope_refs(stat.body)) break; self.condition = make_sequence(self, [ stat.body, self.condition, @@ -5904,6 +5985,18 @@ merge(Compressor.prototype, { body: make_node(AST_EmptyStatement, self) }).optimize(compressor); return self; + + function has_block_scope_refs(node) { + var found = false; + node.walk(new TreeWalker(function(node) { + if (found) return true; + if (node instanceof AST_SymbolRef) { + if (!member(node.definition(), self.enclosed)) found = true; + return true; + } + })); + return found; + } }); function if_break_in_loop(self, compressor) { @@ -5934,7 +6027,7 @@ merge(Compressor.prototype, { } else if (retain) { body.push(first); } - extract_declarations_from_unreachable_code(self.body, body); + extract_declarations_from_unreachable_code(compressor, self.body, body); return make_node(AST_BlockStatement, self, { body: body }); @@ -5952,7 +6045,7 @@ merge(Compressor.prototype, { self.condition = first.condition.negate(compressor); } var body = as_statement_array(first.alternative); - extract_declarations_from_unreachable_code(first.body, body); + extract_declarations_from_unreachable_code(compressor, first.body, body); return drop_it(body); } ab = first_statement(first.alternative); @@ -5967,7 +6060,7 @@ merge(Compressor.prototype, { self.condition = first.condition; } var body = as_statement_array(first.body); - extract_declarations_from_unreachable_code(first.alternative, body); + extract_declarations_from_unreachable_code(compressor, first.alternative, body); return drop_it(body); } } @@ -6015,7 +6108,6 @@ merge(Compressor.prototype, { if (!cond) { if (compressor.option("dead_code")) { var body = []; - extract_declarations_from_unreachable_code(self.body, body); if (self.init instanceof AST_Statement) { body.push(self.init); } else if (self.init) { @@ -6026,6 +6118,7 @@ merge(Compressor.prototype, { body.push(make_node(AST_SimpleStatement, self.condition, { body: self.condition })); + extract_declarations_from_unreachable_code(compressor, self.body, body); return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); } } else if (self.condition && !(cond instanceof AST_Node)) { @@ -6121,21 +6214,23 @@ merge(Compressor.prototype, { } if (!cond) { AST_Node.warn("Condition always false [{file}:{line},{col}]", self.condition.start); - var body = []; - extract_declarations_from_unreachable_code(self.body, body); - body.push(make_node(AST_SimpleStatement, self.condition, { - body: self.condition - })); + var body = [ + make_node(AST_SimpleStatement, self.condition, { + body: self.condition + }), + ]; + extract_declarations_from_unreachable_code(compressor, self.body, body); if (self.alternative) body.push(self.alternative); return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); } else if (!(cond instanceof AST_Node)) { AST_Node.warn("Condition always true [{file}:{line},{col}]", self.condition.start); - var body = []; - if (self.alternative) extract_declarations_from_unreachable_code(self.alternative, body); - body.push(make_node(AST_SimpleStatement, self.condition, { - body: self.condition - })); - body.push(self.body); + var body = [ + make_node(AST_SimpleStatement, self.condition, { + body: self.condition + }), + self.body, + ]; + if (self.alternative) extract_declarations_from_unreachable_code(compressor, self.alternative, body); return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor); } } @@ -6459,7 +6554,7 @@ merge(Compressor.prototype, { if (prev && !aborts(prev)) { prev.body = prev.body.concat(branch.body); } else { - extract_declarations_from_unreachable_code(branch, decl); + extract_declarations_from_unreachable_code(compressor, branch, decl); } } }); @@ -6470,9 +6565,9 @@ merge(Compressor.prototype, { if (has_declarations_only(self)) { var body = []; if (self.bcatch) { - extract_declarations_from_unreachable_code(self.bcatch, body); + extract_declarations_from_unreachable_code(compressor, self.bcatch, body); body.forEach(function(stat) { - if (!(stat instanceof AST_Definitions)) return; + if (!(stat instanceof AST_Var)) return; stat.definitions.forEach(function(var_def) { var def = var_def.name.definition().redefined(); if (!def) return; @@ -6499,7 +6594,13 @@ merge(Compressor.prototype, { return self; }); - AST_Definitions.DEFMETHOD("remove_initializers", function() { + AST_Const.DEFMETHOD("remove_initializers", function(compressor) { + this.definitions.forEach(function(def) { + def.value = make_node(AST_Undefined, def).optimize(compressor); + }); + }); + + AST_Var.DEFMETHOD("remove_initializers", function() { this.definitions.forEach(function(def) { def.value = null; }); @@ -6526,8 +6627,31 @@ merge(Compressor.prototype, { return make_sequence(this, assignments); }); - OPT(AST_Definitions, function(self, compressor) { - return self.definitions.length ? self : make_node(AST_EmptyStatement, self); + OPT(AST_Const, function(self, compressor) { + return all(self.definitions, function(defn) { + var node = defn.name; + if (!node.fixed_value()) return false; + var def = node.definition(); + var scope = def.scope.resolve(); + if (scope instanceof AST_Toplevel) { + if (!compressor.toplevel.vars) return false; + if (def.scope === scope) return true; + return !scope.variables.has(node.name) && !scope.globals.has(node.name); + } + return def.scope === scope || !scope.find_variable(node); + }) ? make_node(AST_Var, self, { + definitions: self.definitions.map(function(defn) { + var name = make_node(AST_SymbolVar, defn.name, defn.name); + var def = name.definition(); + def.orig[def.orig.indexOf(defn.name)] = name; + var scope = def.scope.resolve(); + if (def.scope !== scope) scope.variables.set(def.name, def); + return make_node(AST_VarDef, defn, { + name: name, + value: defn.value + }); + }) + }) : self; }); function lift_sequence_in_expression(node, compressor) { diff --git a/lib/output.js b/lib/output.js index d99c61b8..05baa239 100644 --- a/lib/output.js +++ b/lib/output.js @@ -835,10 +835,6 @@ function OutputStream(options) { use_asm = was_asm; } - AST_StatementWithBody.DEFMETHOD("_do_print_body", function(output) { - force_statement(this.body, output); - }); - DEFPRINT(AST_Statement, function(output) { this.body.print(output); output.semicolon(); @@ -897,7 +893,7 @@ function OutputStream(options) { self.condition.print(output); }); output.space(); - self._do_print_body(output); + force_statement(self.body, output); }); DEFPRINT(AST_For, function(output) { var self = this; @@ -927,7 +923,7 @@ function OutputStream(options) { } }); output.space(); - self._do_print_body(output); + force_statement(self.body, output); }); DEFPRINT(AST_ForIn, function(output) { var self = this; @@ -941,7 +937,7 @@ function OutputStream(options) { self.object.print(output); }); output.space(); - self._do_print_body(output); + force_statement(self.body, output); }); DEFPRINT(AST_With, function(output) { var self = this; @@ -951,7 +947,7 @@ function OutputStream(options) { self.expression.print(output); }); output.space(); - self._do_print_body(output); + force_statement(self.body, output); }); /* -----[ functions ]----- */ @@ -1036,7 +1032,7 @@ function OutputStream(options) { else force_statement(self.alternative, output); } else { - self._do_print_body(output); + force_statement(self.body, output); } }); @@ -1114,17 +1110,21 @@ function OutputStream(options) { print_braced(this, output); }); - DEFPRINT(AST_Var, function(output) { - var self = this; - output.print("var"); - output.space(); - self.definitions.forEach(function(def, i) { - if (i) output.comma(); - def.print(output); - }); - var p = output.parent(); - if (p && p.init !== self || !(p instanceof AST_For || p instanceof AST_ForIn)) output.semicolon(); - }); + function print_definitinos(type) { + return function(output) { + var self = this; + output.print(type); + output.space(); + self.definitions.forEach(function(def, i) { + if (i) output.comma(); + def.print(output); + }); + var p = output.parent(); + if (p && p.init !== self || !(p instanceof AST_For || p instanceof AST_ForIn)) output.semicolon(); + }; + } + DEFPRINT(AST_Const, print_definitinos("const")); + DEFPRINT(AST_Var, print_definitinos("var")); function parenthesize_for_noin(node, output, noin) { var parens = false; @@ -1383,11 +1383,12 @@ function OutputStream(options) { function force_statement(stat, output) { if (output.option("braces")) { make_block(stat, output); + } else if (!stat || stat instanceof AST_EmptyStatement) { + output.force_semicolon(); + } else if (stat instanceof AST_Const) { + make_block(stat, output); } else { - if (!stat || stat instanceof AST_EmptyStatement) - output.force_semicolon(); - else - stat.print(output); + stat.print(output); } } diff --git a/lib/parse.js b/lib/parse.js index f24be3cb..42914d7d 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -832,6 +832,12 @@ function parse($TEXT, options) { next(); return break_cont(AST_Break); + case "const": + next(); + var node = const_(); + semicolon(); + return node; + case "continue": next(); return break_cont(AST_Continue); @@ -988,7 +994,9 @@ function parse($TEXT, options) { expect("("); var init = null; if (!is("punc", ";")) { - init = is("keyword", "var") + init = is("keyword", "const") + ? (next(), const_(true)) + : is("keyword", "var") ? (next(), var_(true)) : expression(true, true); if (is("operator", "in")) { @@ -1161,13 +1169,22 @@ function parse($TEXT, options) { }); } - function vardefs(no_in) { + function vardefs(type, no_in, must_init) { var a = []; for (;;) { + var start = S.token; + var name = as_symbol(type); + var value = null; + if (is("operator", "=")) { + next(); + value = expression(false, no_in); + } else if (must_init) { + croak("Missing initializer in declaration"); + } a.push(new AST_VarDef({ - start : S.token, - name : as_symbol(AST_SymbolVar), - value : is("operator", "=") ? (next(), expression(false, no_in)) : null, + start : start, + name : name, + value : value, end : prev() })); if (!is("punc", ",")) @@ -1177,10 +1194,18 @@ function parse($TEXT, options) { return a; } + var const_ = function(no_in) { + return new AST_Const({ + start : prev(), + definitions : vardefs(AST_SymbolConst, no_in, true), + end : prev() + }); + }; + var var_ = function(no_in) { return new AST_Var({ start : prev(), - definitions : vardefs(no_in), + definitions : vardefs(AST_SymbolVar, no_in), end : prev() }); }; diff --git a/lib/scope.js b/lib/scope.js index ec2c1f6d..fa558640 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -80,7 +80,12 @@ SymbolDef.prototype = { } }, redefined: function() { - return this.defun && this.defun.variables.get(this.name); + var scope = this.defun; + if (!scope) return; + var def = scope.variables.get(this.name); + if (!def && scope instanceof AST_Toplevel) def = scope.globals.get(this.name); + if (def === this) return; + return def; }, unmangleable: function(options) { return this.global && !options.toplevel @@ -114,6 +119,11 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) { }); return true; } + if (node instanceof AST_SwitchBranch) { + node.init_vars(scope); + descend(); + return true; + } if (node instanceof AST_Try) { walk_scope(function() { walk_body(node, tw); @@ -122,10 +132,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) { if (node.bfinally) node.bfinally.walk(tw); return true; } - if (node instanceof AST_BlockScope) { - walk_scope(descend); - return true; - } if (node instanceof AST_With) { var s = scope; do { @@ -133,7 +139,12 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) { if (s.uses_with) break; s.uses_with = true; } while (s = s.parent_scope); - return; + walk_scope(descend); + return true; + } + if (node instanceof AST_BlockScope) { + walk_scope(descend); + return true; } if (node instanceof AST_Symbol) { node.scope = scope; @@ -144,6 +155,8 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) { } if (node instanceof AST_SymbolCatch) { scope.def_variable(node).defun = defun; + } else if (node instanceof AST_SymbolConst) { + scope.def_variable(node).defun = defun; } else if (node instanceof AST_SymbolDefun) { defun.def_function(node, tw.parent()); entangle(defun, scope); @@ -216,7 +229,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) { node.reference(options); return true; } - // ensure mangling works if catch reuses a scope variable + // ensure mangling works if `catch` reuses a scope variable if (node instanceof AST_SymbolCatch) { var def = node.definition().redefined(); if (def) for (var s = node.scope; s; s = s.parent_scope) { @@ -225,6 +238,16 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) { } return true; } + // ensure compression works if `const` reuses a scope variable + if (node instanceof AST_SymbolConst) { + var def = node.definition(); + var redef = def.redefined(); + if (redef) { + if (!redef.const_redefs) redef.const_redefs = []; + redef.const_redefs.push(def); + } + return true; + } }); self.walk(tw); @@ -290,7 +313,6 @@ AST_Toplevel.DEFMETHOD("def_global", function(node) { }); function init_block_vars(scope, parent) { - scope.cname = -1; // the current index for mangling functions/variables scope.enclosed = []; // variables from this or outer scope(s) that are referenced from this or inner scopes scope.parent_scope = parent; // the parent scope (null if this is the top level) scope.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope) @@ -368,8 +390,9 @@ AST_BlockScope.DEFMETHOD("def_variable", function(symbol, init) { function names_in_use(scope, options) { var names = scope.names_in_use; if (!names) { - scope.names_in_use = names = Object.create(null); + scope.cname = -1; scope.cname_holes = []; + scope.names_in_use = names = Object.create(null); var cache = options.cache && options.cache.props; scope.enclosed.forEach(function(def) { if (def.unmangleable(options)) names[def.name] = true; @@ -467,7 +490,11 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options) { lname = save_nesting; return true; } - if (node instanceof AST_Scope) { + if (node instanceof AST_BlockScope) { + var to_mangle = []; + node.variables.each(function(def) { + if (!defer_redef(def)) to_mangle.push(def); + }); descend(); if (options.cache && node instanceof AST_Toplevel) { node.globals.each(mangle); @@ -477,9 +504,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options) { sym.scope = node; sym.reference(options); } - node.variables.each(function(def) { - if (!defer_redef(def)) mangle(def); - }); + to_mangle.forEach(mangle); return true; } if (node instanceof AST_Label) { @@ -490,13 +515,6 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options) { node.mangled_name = name; return true; } - if (!options.ie8 && node instanceof AST_Catch && node.argname) { - var def = node.argname.definition(); - var redef = defer_redef(def, node.argname); - descend(); - if (!redef) mangle(def); - return true; - } }); this.walk(tw); redefined.forEach(mangle); @@ -511,7 +529,8 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options) { if (!redef) return false; redefined.push(def); def.references.forEach(reference); - if (node) reference(node); + var node = def.orig[0]; + if (node instanceof AST_SymbolCatch || node instanceof AST_SymbolConst) reference(node); return true; function reference(sym) { diff --git a/test/compress/asm.js b/test/compress/asm.js index 65d20b9c..331eb426 100644 --- a/test/compress/asm.js +++ b/test/compress/asm.js @@ -76,9 +76,8 @@ asm_mixed: { start = start | 0; end = end | 0; var sum = 0.0, p = 0, q = 0; - for (p = start << 3, q = end << 3; (p | 0) < (q | 0); p = p + 8 | 0) { + for (p = start << 3, q = end << 3; (p | 0) < (q | 0); p = p + 8 | 0) sum = sum + +log(values[p >> 3]); - } return +sum; } function geometricMean(start, end) { @@ -91,7 +90,8 @@ asm_mixed: { function no_asm_GeometricMean(stdlib, foreign, buffer) { function logSum(start, end) { start |= 0, end |= 0; - for (var sum = 0, p = 0, q = 0, p = start << 3, q = end << 3; (0 | p) < (0 | q); p = p + 8 | 0) sum += +log(values[p >> 3]); + for (var sum = 0, p = 0, q = 0, p = start << 3, q = end << 3; (0 | p) < (0 | q); p = p + 8 | 0) + sum += +log(values[p >> 3]); return +sum; } function geometricMean(start, end) { diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 24fadceb..4c3a6eda 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -346,9 +346,8 @@ collapse_vars_if: { return "x" != "Bar" + x / 4 ? g9 : g5; } function f3(x) { - if (x) { + if (x) return 1; - } return 2; } } @@ -4192,9 +4191,8 @@ issue_2436_11: { if (isCollection(arg1)) { var size = arg1, max = arg2, min = 0, res = _randomDataForMatrix(size.valueOf(), min, max, _randomInt); return size && true === size.isMatrix ? matrix(res) : res; - } else { + } else return _randomInt(min = arg1, max = arg2); - } } } } @@ -4310,9 +4308,8 @@ issue_2497: { function sample() { if (true) for (var i = 0; i < 1; ++i) - for (var k = 0; k < 1; ++k) { + for (var k = 0; k < 1; ++k) value = (value = 1) ? value + 1 : 0; - } else for (i = 0; i < 1; ++i) for (k = 0; k < 1; ++k) diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index 5dfd17bc..8a4e3aad 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -55,14 +55,15 @@ ifs_3_should_warn: { } input: { var x, y; - if (x && !(x + "1") && y) { // 1 + // 1 + if (x && !(x + "1") && y) { var qq; foo(); } else { bar(); } - - if (x || !!(x + "1") || y) { // 2 + // 2 + if (x || !!(x + "1") || y) { foo(); } else { var jj; @@ -71,9 +72,27 @@ ifs_3_should_warn: { } expect: { var x, y; - var qq; bar(); // 1 - var jj; foo(); // 2 - } + // 1 + var qq; bar(); + // 2 + foo(); var jj; + } + expect_warnings: [ + "WARN: + in boolean context always true [test/compress/conditionals.js:3,18]", + "WARN: Boolean && always false [test/compress/conditionals.js:3,12]", + "WARN: Condition left of && always false [test/compress/conditionals.js:3,12]", + "WARN: Condition always false [test/compress/conditionals.js:3,12]", + "WARN: Dropping unreachable code [test/compress/conditionals.js:3,34]", + "WARN: Declarations in unreachable code! [test/compress/conditionals.js:4,12]", + "WARN: + in boolean context always true [test/compress/conditionals.js:10,19]", + "WARN: Boolean || always true [test/compress/conditionals.js:10,12]", + "WARN: Condition left of || always true [test/compress/conditionals.js:10,12]", + "WARN: Condition always true [test/compress/conditionals.js:10,12]", + "WARN: Dropping unreachable code [test/compress/conditionals.js:12,15]", + "WARN: Declarations in unreachable code! [test/compress/conditionals.js:13,12]", + "WARN: Dropping side-effect-free statement [test/compress/conditionals.js:3,12]", + "WARN: Dropping side-effect-free statement [test/compress/conditionals.js:10,12]", + ] } ifs_4: { diff --git a/test/compress/const.js b/test/compress/const.js new file mode 100644 index 00000000..265ca9f6 --- /dev/null +++ b/test/compress/const.js @@ -0,0 +1,667 @@ +mangle_catch_1: { + mangle = {} + input: { + try { + throw "eeeee"; + } catch (c) { + const e = typeof d; + } + console.log(typeof a, typeof b); + } + expect: { + try { + throw "eeeee"; + } catch (e) { + const o = typeof d; + } + console.log(typeof a, typeof b); + } + expect_stdout: "undefined undefined" +} + +mangle_catch_2: { + mangle = {} + input: { + console.log(function f() { + try {} catch (e) { + const f = 0; + } + }()); + } + expect: { + console.log(function o() { + try {} catch (c) { + const o = 0; + } + }()); + } + expect_stdout: "undefined" +} + +retain_block: { + options = {} + input: { + { + const a = "FAIL"; + } + var a = "PASS"; + console.log(a); + } + expect: { + { + const a = "FAIL"; + } + var a = "PASS"; + console.log(a); + } + expect_stdout: true +} + +if_dead_branch: { + options = { + conditionals: true, + dead_code: true, + evaluate: true, + } + input: { + console.log(function() { + if (0) { + const a = 0; + } + return typeof a; + }()); + } + expect: { + console.log(function() { + 0; + { + const a = void 0; + } + return typeof a; + }()); + } + expect_stdout: "undefined" +} + +merge_vars_1: { + options = { + merge_vars: true, + toplevel: true, + } + input: { + const a = console; + console.log(typeof a); + var b = typeof a; + console.log(b); + } + expect: { + const a = console; + console.log(typeof a); + var b = typeof a; + console.log(b); + } + expect_stdout: [ + "object", + "object", + ] +} + +merge_vars_2: { + options = { + inline: true, + merge_vars: true, + toplevel: true, + } + input: { + var a = 0; + (function() { + var b = function f() { + const c = a && f; + c.var += 0; + }(); + console.log(b); + })(1 && --a); + } + expect: { + var a = 0; + 1 && --a, + b = function f() { + const c = a && f; + c.var += 0; + }(), + void console.log(b); + var b; + } + expect_stdout: "undefined" +} + +merge_vars_3: { + options = { + merge_vars: true, + toplevel: true, + } + input: { + { + const a = 0; + var b = console; + console.log(typeof b); + } + var a = 1; + console.log(typeof a); + } + expect: { + { + const a = 0; + var b = console; + console.log(typeof b); + } + var a = 1; + console.log(typeof a); + } + expect_stdout: true +} + +reduce_merge_vars: { + options = { + merge_vars: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + const a = console; + console.log(typeof a); + var b = typeof a; + console.log(b); + } + expect: { + var b = console; + console.log(typeof b); + b = typeof b; + console.log(b); + } + expect_stdout: [ + "object", + "object", + ] +} + +use_before_init_1: { + options = { + reduce_vars: true, + toplevel: true, + } + input: { + a = "foo"; + const a = "bar"; + } + expect: { + a = "foo"; + const a = "bar"; + } + expect_stdout: true +} + +use_before_init_2: { + options = { + toplevel: true, + unused: true, + } + input: { + try { + a = "foo"; + } catch (e) { + console.log("PASS"); + } + const a = "bar"; + } + expect: { + try { + a = "foo"; + } catch (e) { + console.log("PASS"); + } + const a = "bar"; + } + expect_stdout: true +} + +use_before_init_3: { + options = { + side_effects: true, + } + input: { + try { + a; + } catch (e) { + console.log("PASS"); + } + const a = 42; + } + expect: { + try { + a; + } catch (e) { + console.log("PASS"); + } + const a = 42; + } + expect_stdout: true +} + +use_before_init_4: { + options = { + reduce_vars: true, + } + input: { + try { + console.log(a); + } catch (e) { + console.log("PASS"); + } + const a = "FAIL"; + } + expect: { + try { + console.log(a); + } catch (e) { + console.log("PASS"); + } + const a = "FAIL"; + } + expect_stdout: true +} + +collapse_block: { + options = { + collapse_vars: true, + pure_getters: "strict", + unsafe: true, + } + input: { + { + const a = typeof console; + console.log(a); + } + } + expect: { + { + const a = typeof console; + console.log(a); + } + } + expect_stdout: "object" +} + +reduce_block_1: { + options = { + reduce_vars: true, + } + input: { + { + const a = typeof console; + console.log(a); + } + } + expect: { + { + const a = typeof console; + console.log(a); + } + } + expect_stdout: "object" +} + +reduce_block_1_toplevel: { + options = { + reduce_vars: true, + toplevel: true, + } + input: { + { + const a = typeof console; + console.log(a); + } + } + expect: { + var a = typeof console; + console.log(a); + } + expect_stdout: "object" +} + +reduce_block_2: { + options = { + reduce_vars: true, + } + input: { + { + const a = typeof console; + console.log(a); + } + console.log(typeof a); + } + expect: { + { + const a = typeof console; + console.log(a); + } + console.log(typeof a); + } + expect_stdout: true +} + +reduce_block_2_toplevel: { + options = { + reduce_vars: true, + toplevel: true, + } + input: { + { + const a = typeof console; + console.log(a); + } + console.log(typeof a); + } + expect: { + { + const a = typeof console; + console.log(a); + } + console.log(typeof a); + } + expect_stdout: true +} + +hoist_props_1: { + options = { + hoist_props: true, + reduce_vars: true, + } + input: { + { + const o = { + p: "PASS", + }; + console.log(o.p); + } + } + expect: { + { + const o = { + p: "PASS", + }; + console.log(o.p); + } + } + expect_stdout: "PASS" +} + +hoist_props_2: { + options = { + hoist_props: true, + passes: 2, + reduce_vars: true, + toplevel: true, + } + input: { + { + const o = { + p: "PASS", + }; + console.log(o.p); + } + } + expect: { + var o_p = "PASS"; + console.log(o_p); + } + expect_stdout: "PASS" +} + +loop_block_1: { + options = { + loops: true, + } + input: { + do { + const o = console; + console.log(typeof o.log); + } while (!console); + } + expect: { + do { + const o = console; + console.log(typeof o.log); + } while (!console); + } + expect_stdout: "function" +} + +loop_block_2: { + options = { + loops: true, + } + input: { + do { + const o = {}; + (function() { + console.log(typeof this, o.p++); + })(); + } while (!console); + } + expect: { + do { + const o = {}; + (function() { + console.log(typeof this, o.p++); + })(); + } while (!console); + } + expect_stdout: "object NaN" +} + +do_continue: { + options = { + loops: true, + } + input: { + try { + do { + { + const a = 0; + continue; + } + } while ([ A ]); + } catch (e) { + console.log("PASS"); + } + } + expect: { + try { + do { + const a = 0; + continue; + } while ([ A ]); + } catch (e) { + console.log("PASS"); + } + } + expect_stdout: "PASS" +} + +catch_ie8_1: { + options = { + ie8: true, + unused: true, + } + input: { + try {} catch (a) {} + console.log(function a() { + const a = 0; + }()); + } + expect: { + try {} catch (a) {} + console.log(function a() { + }()); + } + expect_stdout: "undefined" +} + +catch_ie8_2: { + options = { + dead_code: true, + ie8: true, + toplevel: true, + unused: true, + } + input: { + try {} catch (a) { + const b = 0; + } + try {} catch (b) {} + console.log(function() { + return this; + }().b); + } + expect: { + console.log(function() { + return this; + }().b); + } + expect_stdout: "undefined" +} + +dead_block_after_return: { + options = { + dead_code: true, + } + input: { + (function(a) { + console.log(a); + return; + { + const a = 0; + } + })(); + } + expect: { + (function(a) { + console.log(a); + return; + { + const a = void 0; + } + })(); + } + expect_stdout: true +} + +const_to_var_scope_adjustment: { + options = { + conditionals: true, + inline: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + for (var k in [ 42 ]) + console.log(function f() { + if (k) { + const a = 0; + } + }()); + } + expect: { + for (var k in [ 42 ]) + console.log(void (k && 0)); + } + expect_stdout: "undefined" +} + +do_if_continue_1: { + options = { + if_return: true, + } + input: { + do { + if (console) { + console.log("PASS"); + { + const a = 0; + var b; + continue; + } + } + } while (b); + } + expect: { + do { + if (!console); + else { + console.log("PASS"); + { + const a = 0; + var b; + } + } + } while (b); + } + expect_stdout: "PASS" +} + +do_if_continue_2: { + options = { + if_return: true, + } + input: { + do { + if (console) { + console.log("PASS"); + { + const a = 0; + A = 0; + continue; + } + } + } while (A); + } + expect: { + do { + if (!console); + else { + console.log("PASS"); + { + const a = 0; + A = 0; + } + } + } while (A); + } + expect_stdout: "PASS" +} + +drop_unused: { + options = { + evaluate: true, + side_effects: true, + unused: true, + } + input: { + function f(a) { + const b = a, c = b; + 0 && c.p++; + } + console.log(f()); + } + expect: { + function f(a) { + const b = a; + b; + } + console.log(f()); + } + expect_stdout: "undefined" +} diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index 89829b65..3a5a4527 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -59,6 +59,11 @@ dead_code_2_should_warn: { f(); } expect_stdout: true + expect_warnings: [ + "WARN: Dropping unreachable code [test/compress/dead-code.js:8,12]", + "WARN: Declarations in unreachable code! [test/compress/dead-code.js:10,16]", + "WARN: Dropping unreachable code [test/compress/dead-code.js:10,16]", + ] node_version: "<=4" } @@ -89,11 +94,23 @@ dead_code_constant_boolean_should_warn_more: { function bar() {} // nothing for the while // as for the for, it should keep: - var moo; var x = 10, y; + var moo; bar(); } expect_stdout: true + expect_warnings: [ + "WARN: + in boolean context always true [test/compress/dead-code.js:1,33]", + "WARN: Boolean || always true [test/compress/dead-code.js:1,16]", + "WARN: Dropping unreachable code [test/compress/dead-code.js:1,45]", + "WARN: Declarations in unreachable code! [test/compress/dead-code.js:3,12]", + "WARN: Boolean expression always true [test/compress/dead-code.js:6,47]", + "WARN: Boolean && always false [test/compress/dead-code.js:6,28]", + "WARN: Dropping unreachable code [test/compress/dead-code.js:6,63]", + "WARN: Declarations in unreachable code! [test/compress/dead-code.js:9,12]", + "WARN: Dropping side-effect-free statement [test/compress/dead-code.js:1,15]", + "WARN: Dropping side-effect-free statement [test/compress/dead-code.js:6,28]", + ] node_version: "<=4" } diff --git a/test/compress/issue-1034.js b/test/compress/issue-1034.js index 137aed8c..db488d05 100644 --- a/test/compress/issue-1034.js +++ b/test/compress/issue-1034.js @@ -90,13 +90,13 @@ non_hoisted_function_after_return_2a: { "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:7,16]", "WARN: Dropping unused variable a [test/compress/issue-1034.js:4,20]", "WARN: Dropping unused function nope [test/compress/issue-1034.js:11,21]", - "INFO: pass 0: last_count: Infinity, count: 36", + "INFO: pass 0: last_count: Infinity, count: 35", "WARN: Dropping unreachable code [test/compress/issue-1034.js:9,12]", "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:9,12]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:12,12]", "INFO: Dropping unused variable b [test/compress/issue-1034.js:7,20]", "INFO: Dropping unused variable c [test/compress/issue-1034.js:9,16]", - "INFO: pass 1: last_count: 36, count: 18", + "INFO: pass 1: last_count: 35, count: 18", ] } @@ -248,13 +248,13 @@ non_hoisted_function_after_return_2a_strict: { "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:8,16]", "WARN: Dropping unused variable a [test/compress/issue-1034.js:5,20]", "WARN: Dropping unused function nope [test/compress/issue-1034.js:12,21]", - "INFO: pass 0: last_count: Infinity, count: 47", + "INFO: pass 0: last_count: Infinity, count: 46", "WARN: Dropping unreachable code [test/compress/issue-1034.js:10,12]", "WARN: Declarations in unreachable code! [test/compress/issue-1034.js:10,12]", "WARN: Dropping unreachable code [test/compress/issue-1034.js:13,12]", "INFO: Dropping unused variable b [test/compress/issue-1034.js:8,20]", "INFO: Dropping unused variable c [test/compress/issue-1034.js:10,16]", - "INFO: pass 1: last_count: 47, count: 29", + "INFO: pass 1: last_count: 46, count: 29", ] } diff --git a/test/compress/loops.js b/test/compress/loops.js index 1fa3d00c..cfabe6fd 100644 --- a/test/compress/loops.js +++ b/test/compress/loops.js @@ -547,8 +547,8 @@ dead_code_condition: { console.log(a); } expect: { - var c; var a = 0, b = 5; + var c; a += 1, 0, console.log(a); } @@ -1197,3 +1197,28 @@ issue_4182_2: { } expect_stdout: "PASS" } + +do_continue: { + options = { + loops: true, + } + input: { + try { + do { + continue; + } while ([ A ]); + } catch (e) { + console.log("PASS"); + } + } + expect: { + try { + do { + continue; + } while ([ A ]); + } catch (e) { + console.log("PASS"); + } + } + expect_stdout: "PASS" +} diff --git a/test/compress/pure_getters.js b/test/compress/pure_getters.js index 9764f0f4..25efd7fd 100644 --- a/test/compress/pure_getters.js +++ b/test/compress/pure_getters.js @@ -848,9 +848,8 @@ collapse_vars_1_true: { } expect: { function f(a, b) { - for (;;) { + for (;;) if (a.g() || b.p) break; - } } } } diff --git a/test/reduce.js b/test/reduce.js index f1d2b448..ec1fb0bc 100644 --- a/test/reduce.js +++ b/test/reduce.js @@ -121,7 +121,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options) return; } // preserve for (var xxx; ...) - if (parent instanceof U.AST_For && parent.init === node && node instanceof U.AST_Var) return node; + if (parent instanceof U.AST_For && parent.init === node && node instanceof U.AST_Definitions) return node; // preserve for (xxx in ...) if (parent instanceof U.AST_ForIn && parent.init === node) return node; @@ -145,7 +145,9 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options) return permute < 2 ? expr : wrap_with_console_log(expr); } else if (node instanceof U.AST_BlockStatement) { - if (in_list) { + if (in_list && node.body.filter(function(node) { + return node instanceof U.AST_Const; + }).length == 0) { node.start._permute++; CHANGED = true; return List.splice(node.body); @@ -410,7 +412,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options) start: {}, }); } - else if (node instanceof U.AST_Var) { + else if (node instanceof U.AST_Definitions) { // remove empty var statement if (node.definitions.length == 0) return in_list ? List.skip : new U.AST_EmptyStatement({ start: {}, diff --git a/test/ufuzz/index.js b/test/ufuzz/index.js index 60f86667..717252d3 100644 --- a/test/ufuzz/index.js +++ b/test/ufuzz/index.js @@ -275,6 +275,7 @@ var CANNOT_RETURN = true; var NO_DEFUN = false; var DEFUN_OK = true; var DONT_STORE = true; +var NO_CONST = true; var VAR_NAMES = [ "a", @@ -312,6 +313,7 @@ var TYPEOF_OUTCOMES = [ "crap", ]; +var block_vars = []; var unique_vars = []; var loops = 0; var funcs = 0; @@ -374,33 +376,66 @@ function filterDirective(s) { return s; } +function createBlockVariables(recurmax, stmtDepth, canThrow, fn) { + var block_len = block_vars.length; + var var_len = VAR_NAMES.length; + var consts = []; + unique_vars.push("a", "b", "c", "undefined", "NaN", "Infinity"); + while (!rng(block_vars.length > block_len ? 10 : 100)) { + var name = createVarName(MANDATORY, DONT_STORE); + consts.push(name); + block_vars.push(name); + } + unique_vars.length -= 6; + fn(function() { + var s = []; + if (consts.length) { + var save = VAR_NAMES; + VAR_NAMES = VAR_NAMES.filter(function(name) { + return consts.indexOf(name) < 0; + }); + var len = VAR_NAMES.length; + s.push("const " + consts.map(function(name) { + var value = createExpression(recurmax, NO_COMMA, stmtDepth, canThrow); + VAR_NAMES.push(name); + return name + " = " + value; + }).join(", ") + ";"); + VAR_NAMES = save.concat(VAR_NAMES.slice(len)); + } + return s.join("\n"); + }); + block_vars.length = block_len; + if (consts.length) VAR_NAMES.splice(var_len, consts.length); +} + function createFunction(recurmax, allowDefun, canThrow, stmtDepth) { if (--recurmax < 0) { return ";"; } if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0; - var namesLenBefore = VAR_NAMES.length; + var s = []; var name; - if (allowDefun || rng(5) > 0) { - name = "f" + funcs++; - } else { - unique_vars.push("a", "b", "c"); - name = createVarName(MANDATORY, !allowDefun); - unique_vars.length -= 3; - } - var s = [ - "function " + name + "(" + createParams() + "){", - strictMode() - ]; - if (rng(5) === 0) { - // functions with functions. lower the recursion to prevent a mess. - s.push(createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), DEFUN_OK, canThrow, stmtDepth)); - } else { - // functions with statements - s.push(createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth)); - } - s.push("}", ""); - s = filterDirective(s).join("\n"); + createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) { + var namesLenBefore = VAR_NAMES.length; + if (allowDefun || rng(5) > 0) { + name = "f" + funcs++; + } else { + unique_vars.push("a", "b", "c"); + name = createVarName(MANDATORY, !allowDefun); + unique_vars.length -= 3; + } + s.push("function " + name + "(" + createParams() + "){", strictMode()); + s.push(defns()); + if (rng(5) === 0) { + // functions with functions. lower the recursion to prevent a mess. + s.push(createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), DEFUN_OK, canThrow, stmtDepth)); + } else { + // functions with statements + s.push(_createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth)); + } + s.push("}", ""); + s = filterDirective(s).join("\n"); - VAR_NAMES.length = namesLenBefore; + VAR_NAMES.length = namesLenBefore; + }); if (!allowDefun) { // avoid "function statements" (decl inside statements) @@ -414,7 +449,7 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) { return s + ";"; } -function createStatements(n, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) { +function _createStatements(n, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) { if (--recurmax < 0) { return ";"; } var s = ""; while (--n > 0) { @@ -423,6 +458,15 @@ function createStatements(n, recurmax, canThrow, canBreak, canContinue, cannotRe return s; } +function createStatements(n, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) { + var s = ""; + createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) { + s += defns() + "\n"; + s += _createStatements(n, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth); + }); + return s; +} + function enableLoopControl(flag, defaultValue) { return Array.isArray(flag) && flag.indexOf("") < 0 ? flag.concat("") : flag || defaultValue; } @@ -496,7 +540,7 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn var label = createLabel(canBreak, canContinue); canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK); canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE); - var key = rng(10) ? "key" + loop : getVarName(); + var key = rng(10) ? "key" + loop : getVarName(NO_CONST); return [ "{var expr" + loop + " = " + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "; ", label.target + " for (", @@ -571,13 +615,18 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn // the catch var should only be accessible in the catch clause... // we have to do go through some trouble here to prevent leaking it var nameLenBefore = VAR_NAMES.length; - var catchName = createVarName(MANDATORY); - var freshCatchName = VAR_NAMES.length !== nameLenBefore; - if (!catch_redef) unique_vars.push(catchName); - s += " catch (" + catchName + ") { " + createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + " }"; - // remove catch name - if (!catch_redef) unique_vars.pop(); - if (freshCatchName) VAR_NAMES.splice(nameLenBefore, 1); + createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) { + var catchName = createVarName(MANDATORY); + var freshCatchName = VAR_NAMES.length !== nameLenBefore; + if (!catch_redef) unique_vars.push(catchName); + s += " catch (" + catchName + ") { "; + s += defns() + "\n"; + s += _createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth); + s += " }"; + // remove catch name + if (!catch_redef) unique_vars.pop(); + if (freshCatchName) VAR_NAMES.splice(nameLenBefore, 1); + }); } if (n !== 0) s += " finally { " + createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + " }"; return s; @@ -597,7 +646,7 @@ function createSwitchParts(recurmax, n, canThrow, canBreak, canContinue, cannotR if (hadDefault || rng(5) > 0) { s.push( "case " + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ":", - createStatements(rng(3) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth), + _createStatements(rng(3) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth), rng(10) > 0 ? " break;" : "/* fall-through */", "" ); @@ -605,7 +654,7 @@ function createSwitchParts(recurmax, n, canThrow, canBreak, canContinue, cannotR hadDefault = true; s.push( "default:", - createStatements(rng(3) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth), + _createStatements(rng(3) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth), "" ); } @@ -653,7 +702,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { case p++: return getVarName(); case p++: - return getVarName() + createAssignment() + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow); + return getVarName(NO_CONST) + createAssignment() + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow); case p++: return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow); case p++: @@ -699,19 +748,22 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { ); break; default: - var instantiate = rng(4) ? "new " : ""; - s.push( - instantiate + "function " + name + "(){", - strictMode() - ); - if (instantiate) for (var i = rng(4); --i >= 0;) { - if (rng(2)) s.push("this." + getDotKey(true) + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ";"); - else s.push("this[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "]" + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ";"); - } - s.push( - createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), - rng(2) == 0 ? "}" : "}()" - ); + createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) { + var instantiate = rng(4) ? "new " : ""; + s.push( + instantiate + "function " + name + "(){", + strictMode(), + defns() + ); + if (instantiate) for (var i = rng(4); --i >= 0;) { + if (rng(2)) s.push("this." + getDotKey(true) + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ";"); + else s.push("this[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "]" + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ";"); + } + s.push( + _createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), + rng(2) == 0 ? "}" : "}()" + ); + }); break; } VAR_NAMES.length = nameLenBefore; @@ -861,13 +913,16 @@ function createAccessor(recurmax, stmtDepth, canThrow) { do { prop2 = getDotKey(); } while (prop1 == prop2); - s = [ - "set " + prop1 + "(" + createVarName(MANDATORY) + "){", - strictMode(), - createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), - "this." + prop2 + createAssignment() + _createBinaryExpr(recurmax, COMMA_OK, stmtDepth, canThrow) + ";", - "}," - ]; + createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) { + s = [ + "set " + prop1 + "(" + createVarName(MANDATORY) + "){", + strictMode(), + defns(), + _createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), + "this." + prop2 + createAssignment() + _createBinaryExpr(recurmax, COMMA_OK, stmtDepth, canThrow) + ";", + "}," + ]; + }); } VAR_NAMES.length = namesLenBefore; return filterDirective(s).join("\n"); @@ -906,7 +961,7 @@ function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) { case 1: return "(" + createUnarySafePrefix() + "(" + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + "))"; case 2: - assignee = getVarName(); + assignee = getVarName(NO_CONST); return "(" + assignee + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")"; case 3: assignee = getVarName(); @@ -968,9 +1023,10 @@ function createUnaryPostfix() { return UNARY_POSTFIX[rng(UNARY_POSTFIX.length)]; } -function getVarName() { +function getVarName(noConst) { // try to get a generated name reachable from current scope. default to just `a` - return VAR_NAMES[INITIAL_NAMES_LEN + rng(VAR_NAMES.length - INITIAL_NAMES_LEN)] || "a"; + var name = VAR_NAMES[INITIAL_NAMES_LEN + rng(VAR_NAMES.length - INITIAL_NAMES_LEN)]; + return !name || noConst && block_vars.indexOf(name) >= 0 ? "a" : name; } function createVarName(maybe, dontStore) { @@ -980,7 +1036,7 @@ function createVarName(maybe, dontStore) { do { name = VAR_NAMES[rng(VAR_NAMES.length)]; if (suffix) name += "_" + suffix; - } while (unique_vars.indexOf(name) >= 0); + } while (unique_vars.indexOf(name) >= 0 || block_vars.indexOf(name) >= 0); if (suffix && !dontStore) VAR_NAMES.push(name); return name; } @@ -1258,10 +1314,6 @@ function patch_try_catch(orig, toplevel) { } } -var fallback_options = [ JSON.stringify({ - compress: false, - mangle: false -}) ]; var minify_options = require("./options.json").map(JSON.stringify); var original_code, original_result, errored; var uglify_code, uglify_result, ok; @@ -1269,10 +1321,19 @@ for (var round = 1; round <= num_iterations; round++) { process.stdout.write(round + " of " + num_iterations + "\r"); original_code = createTopLevelCode(); - var orig_result = [ sandbox.run_code(original_code) ]; + var orig_result = [ sandbox.run_code(original_code), sandbox.run_code(original_code, true) ]; errored = typeof orig_result[0] != "string"; - if (!errored) orig_result.push(sandbox.run_code(original_code, true)); - (errored ? fallback_options : minify_options).forEach(function(options) { + if (errored) { + println("//============================================================="); + println("// original code"); + try_beautify(original_code, false, orig_result[0], println); + println(); + println(); + println("original result:"); + println(orig_result[0]); + println(); + } + minify_options.forEach(function(options) { var o = JSON.parse(options); var toplevel = sandbox.has_toplevel(o); o.validate = true; @@ -1294,6 +1355,8 @@ for (var round = 1; round <= num_iterations; round++) { ok = sandbox.same_stdout(fuzzy_result, uglify_result); } } + // ignore difference in error message caused by Temporal Dead Zone + if (!ok && errored) ok = uglify_result.name == "ReferenceError" && original_result.name == "ReferenceError"; // ignore difference in error message caused by `in` // ignore difference in depth of termination caused by infinite recursion if (!ok) { @@ -1308,16 +1371,6 @@ for (var round = 1; round <= num_iterations; round++) { ok = errored && uglify_code.name == original_result.name; } if (verbose || (verbose_interval && !(round % INTERVAL_COUNT)) || !ok) log(options); - else if (errored) { - println("//============================================================="); - println("// original code"); - try_beautify(original_code, toplevel, original_result, println); - println(); - println(); - println("original result:"); - println(original_result); - println(); - } if (!ok && isFinite(num_iterations)) { println(); process.exit(1);