diff --git a/lib/ast.js b/lib/ast.js index 9b243f16..99415446 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -91,19 +91,23 @@ var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos }, null); var AST_Node = DEFNODE("Node", "start end", { - _clone: function(deep) { + _clone: function(deep, depth) { + if (typeof depth == "undefined") depth = 0; + if (depth++ > 1000) { + throw new Error("clone depth exceeded (" + this.start.line + ", " + this.start.col + ")"); + } if (deep) { - var self = this.clone(); + var self = this.clone(false, depth); return self.transform(new TreeTransformer(function(node) { if (node !== self) { - return node.clone(true); + return node.clone(true, depth); } })); } return new this.CTOR(this); }, - clone: function(deep) { - return this._clone(deep); + clone: function(deep, depth) { + return this._clone(deep, depth); }, $documentation: "Base class of all AST nodes", $propdoc: { @@ -202,8 +206,8 @@ var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", { this.body._walk(visitor); }); }, - clone: function(deep) { - var node = this._clone(deep); + clone: function(deep, depth) { + var node = this._clone(deep, depth); if (deep) { var label = node.label; var def = this.label; diff --git a/lib/compress.js b/lib/compress.js index c1232420..332bb68a 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -4243,7 +4243,11 @@ merge(Compressor.prototype, { } if (fixed && d.single_use) { var value = fixed.optimize(compressor); - return value === fixed ? fixed.clone(true) : value; + try { + return value === fixed ? fixed.clone(true) : value; + } catch (x) { + // infinite clone - do nothing + } } if (fixed && d.should_replace === undefined) { var init; diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 25f95ff8..fa455e7d 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -3654,3 +3654,183 @@ issue_2440_with_2: { } } } + +recursive_inlining_1: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + !function(){ + function foo() { bar(); } + function bar() { foo(); } + console.log("PASS"); + }(); + } + expect: { + !function() { + console.log("PASS"); + }(); + } +} + +recursive_inlining_2: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + !function(){ + function foo() { qux(); } + function bar() { foo(); } + function qux() { bar(); } + console.log("PASS"); + }(); + } + expect: { + !function() { + console.log("PASS"); + }(); + } +} + +recursive_inlining_3: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + !function() { + function foo(x) { console.log("foo", x); if (x) bar(x-1); } + function bar(x) { console.log("bar", x); if (x) qux(x-1); } + function qux(x) { console.log("qux", x); if (x) foo(x-1); } + qux(4); + }(); + } + expect: { + !function() { + function qux(x) { + console.log("qux", x); + if (x) (function(x) { + console.log("foo", x); + if (x) (function(x) { + console.log("bar", x); + if (x) qux(x - 1); + })(x - 1); + })(x - 1); + } + qux(4); + }(); + } + expect_stdout: [ + "qux 4", + "foo 3", + "bar 2", + "qux 1", + "foo 0", + ] +} + +recursive_inlining_4: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + !function() { + function foo(x) { console.log("foo", x); if (x) bar(x-1); } + function bar(x) { console.log("bar", x); if (x) qux(x-1); } + function qux(x) { console.log("qux", x); if (x) foo(x-1); } + qux(4); + bar(5); + }(); + } + expect: { + !function() { + function bar(x) { + console.log("bar", x); + if (x) qux(x - 1); + } + function qux(x) { + console.log("qux", x); + if (x) (function(x) { + console.log("foo", x); + if (x) bar(x - 1); + })(x - 1); + } + qux(4); + bar(5); + }(); + } + expect_stdout: [ + "qux 4", + "foo 3", + "bar 2", + "qux 1", + "foo 0", + "bar 5", + "qux 4", + "foo 3", + "bar 2", + "qux 1", + "foo 0", + ] +} + +recursive_inlining_5: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + !function() { + function foo(x) { console.log("foo", x); if (x) bar(x-1); } + function bar(x) { console.log("bar", x); if (x) qux(x-1); } + function qux(x) { console.log("qux", x); if (x) foo(x-1); } + qux(4); + bar(5); + foo(3); + }(); + } + expect: { + !function() { + function foo(x) { + console.log("foo", x); + if (x) bar(x - 1); + } + function bar(x) { + console.log("bar", x); + if (x) qux(x - 1); + } + function qux(x) { + console.log("qux", x); + if (x) foo(x - 1); + } + qux(4); + bar(5); + foo(3); + }(); + } + expect_stdout: [ + "qux 4", + "foo 3", + "bar 2", + "qux 1", + "foo 0", + "bar 5", + "qux 4", + "foo 3", + "bar 2", + "qux 1", + "foo 0", + "foo 3", + "bar 2", + "qux 1", + "foo 0", + ] +}