Prevent JsInliner from triggering infinite expansion.
Now that JsStaticEval will turn "if (blah) {a()}" into "blah && a()" the functions can then be inlined by JsInliner. There was already code to blacklist self-recursive functions, but there was no code to detect a function that has become self-recursive during the inlining process.
Patch by: bobv
Review by: scottb
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5585 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/js/JsInliner.java b/dev/core/src/com/google/gwt/dev/js/JsInliner.java
index f3e974c..1379f8a 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsInliner.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsInliner.java
@@ -728,11 +728,10 @@
private final JsProgram program;
/**
- * A map containing the next integer to try as an identifier suffix for
- * a given JsScope.
+ * A map containing the next integer to try as an identifier suffix for a
+ * given JsScope.
*/
- private IdentityHashMap<JsScope,HashMap<String,Integer>> startIdentForScope
- = new IdentityHashMap<JsScope, HashMap<String,Integer>>();
+ private IdentityHashMap<JsScope, HashMap<String, Integer>> startIdentForScope = new IdentityHashMap<JsScope, HashMap<String, Integer>>();
/**
* Not a stack because program fragments aren't nested.
@@ -851,6 +850,7 @@
if (functionStack.isEmpty()) {
return;
}
+ JsFunction callerFunction = functionStack.peek();
/*
* We only want to look at invocations of things that we statically know
@@ -869,6 +869,15 @@
return;
}
+ /*
+ * The current function has been mutated so as to be self-recursive. Ban
+ * it from any future inlining to prevent infinite expansion.
+ */
+ if (f == callerFunction) {
+ blacklist.add(f);
+ return;
+ }
+
List<JsStatement> statements;
if (f.getBody() != null) {
statements = new ArrayList<JsStatement>(f.getBody().getStatements());
@@ -954,7 +963,7 @@
}
// Confirm that the expression conforms to the desired heuristics
- if (!isInlinable(program, functionStack.peek(), x, f, op)) {
+ if (!isInlinable(program, callerFunction, f, x.getArguments(), op)) {
return;
}
@@ -970,13 +979,13 @@
*/
String ident;
String base = f.getName() + "_" + name.getIdent();
- JsScope scope = functionStack.peek().getScope();
- HashMap<String,Integer> startIdent = startIdentForScope.get(scope);
+ JsScope scope = callerFunction.getScope();
+ HashMap<String, Integer> startIdent = startIdentForScope.get(scope);
if (startIdent == null) {
- startIdent = new HashMap<String,Integer>();
+ startIdent = new HashMap<String, Integer>();
startIdentForScope.put(scope, startIdent);
}
-
+
Integer s = startIdent.get(base);
int suffix = (s == null) ? 0 : s.intValue();
do {
@@ -1004,8 +1013,7 @@
return;
}
- if (functionStack.peek() == programFunction
- && localVariableNames.size() > 0) {
+ if (callerFunction == programFunction && localVariableNames.size() > 0) {
// Don't add additional variables to the top-level program.
return;
}
@@ -1023,7 +1031,6 @@
* total number of passes required to finalize the AST.
*/
op = accept(op);
-
ctx.replaceMe(op);
}
@@ -1704,8 +1711,7 @@
* Determine if a statement can be inlined into a call site.
*/
private static boolean isInlinable(JsProgram program, JsFunction caller,
- JsInvocation invocation, JsFunction callee, JsNode<?> toInline) {
- List<JsExpression> arguments = invocation.getArguments();
+ JsFunction callee, List<JsExpression> arguments, JsNode<?> toInline) {
/*
* This will happen with varargs-style JavaScript functions that rely on the
diff --git a/dev/core/test/com/google/gwt/dev/js/JsInlinerTest.java b/dev/core/test/com/google/gwt/dev/js/JsInlinerTest.java
new file mode 100644
index 0000000..bcbe315
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/js/JsInlinerTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.js;
+
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsExpression;
+import com.google.gwt.dev.js.ast.JsFunction;
+import com.google.gwt.dev.js.ast.JsModVisitor;
+import com.google.gwt.dev.js.ast.JsName;
+import com.google.gwt.dev.js.ast.JsProgram;
+
+/**
+ * Safety checks for JsInliner.
+ */
+public class JsInlinerTest extends OptimizerTestBase {
+
+ public void testSelfRecursion() throws Exception {
+ String input = "function a1() { return blah && b1() }"
+ + "function b1() { return bar && a1()}" + "function c() { a1() } c()";
+ input = optimize(input, JsSymbolResolver.class, FixStaticRefsVisitor.class,
+ JsInliner.class, JsUnusedFunctionRemover.class);
+
+ String expected = "function a1() { return blah && bar && a1() }"
+ + "function c() { a1() } c()";
+ expected = optimize(expected);
+ assertEquals(expected, input);
+ }
+
+ private static class FixStaticRefsVisitor extends JsModVisitor {
+ @Override
+ public void endVisit(JsFunction x, JsContext<JsExpression> ctx) {
+ JsName name = x.getName();
+ name.setStaticRef(x);
+ }
+
+ public static void exec(JsProgram program) {
+ (new FixStaticRefsVisitor()).accept(program);
+ }
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/js/JsStaticEvalTest.java b/dev/core/test/com/google/gwt/dev/js/JsStaticEvalTest.java
index 7f7ba74..271f174 100644
--- a/dev/core/test/com/google/gwt/dev/js/JsStaticEvalTest.java
+++ b/dev/core/test/com/google/gwt/dev/js/JsStaticEvalTest.java
@@ -15,19 +15,10 @@
*/
package com.google.gwt.dev.js;
-import com.google.gwt.dev.jjs.SourceOrigin;
-import com.google.gwt.dev.js.ast.JsProgram;
-import com.google.gwt.dev.js.ast.JsStatement;
-import com.google.gwt.dev.js.ast.JsVisitor;
-import com.google.gwt.dev.util.DefaultTextOutput;
-import com.google.gwt.dev.util.TextOutput;
-
-import junit.framework.TestCase;
-
-import java.io.StringReader;
-import java.util.List;
-
-public class JsStaticEvalTest extends TestCase {
+/**
+ * Tests the JsStaticEval optimizer.
+ */
+public class JsStaticEvalTest extends OptimizerTestBase {
public void testIfWithEmptyThen() throws Exception {
assertEquals("a();", optimize("if (a()) { }"));
@@ -81,18 +72,6 @@
}
private String optimize(String js) throws Exception {
- JsProgram program = new JsProgram();
- List<JsStatement> expected = JsParser.parse(SourceOrigin.UNKNOWN,
- program.getScope(), new StringReader(js));
- program.getGlobalBlock().getStatements().addAll(expected);
-
- // Run the static evaluation over this new program
- JsStaticEval.exec(program);
-
- TextOutput text = new DefaultTextOutput(true);
- JsVisitor generator = new JsSourceGenerationVisitor(text);
-
- generator.accept(program);
- return text.toString();
+ return optimize(js, JsStaticEval.class);
}
}
diff --git a/dev/core/test/com/google/gwt/dev/js/OptimizerTestBase.java b/dev/core/test/com/google/gwt/dev/js/OptimizerTestBase.java
new file mode 100644
index 0000000..e04b62a
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/js/OptimizerTestBase.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.js;
+
+import com.google.gwt.dev.jjs.SourceOrigin;
+import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsStatement;
+import com.google.gwt.dev.js.ast.JsVisitor;
+import com.google.gwt.dev.util.DefaultTextOutput;
+import com.google.gwt.dev.util.TextOutput;
+
+import junit.framework.TestCase;
+
+import java.io.StringReader;
+import java.lang.reflect.Method;
+import java.util.List;
+
+/**
+ * A utility base type for writing tests for JS optimizers.
+ */
+public abstract class OptimizerTestBase extends TestCase {
+
+ /**
+ * Optimize a JS program.
+ *
+ * @param js the source program
+ * @param toExec a list of classes that implement
+ * <code>static void exec(JsProgram)</code>
+ * @return optimized JS
+ */
+ protected String optimize(String js, Class<?>... toExec) throws Exception {
+ JsProgram program = new JsProgram();
+ List<JsStatement> expected = JsParser.parse(SourceOrigin.UNKNOWN,
+ program.getScope(), new StringReader(js));
+
+ program.getGlobalBlock().getStatements().addAll(expected);
+
+ for (Class<?> clazz : toExec) {
+ Method m = clazz.getMethod("exec", JsProgram.class);
+ m.invoke(null, program);
+ }
+
+ TextOutput text = new DefaultTextOutput(true);
+ JsVisitor generator = new JsSourceGenerationVisitor(text);
+
+ generator.accept(program);
+ return text.toString();
+ }
+}
\ No newline at end of file