Fixes JsDuplicateFunctionRemover to be more robust.
Any JsNameRef JsName that is *not* the immediate LHS of a JsInvocation is blacklisted, and therefore, cannot be replaced nor replace another ref. For example, defineSeed(ctor1, ctor2) would stop ctor1 or ctor2 from being considered, and so would ctor1.prototype = ... .
It looks like JsNameOf nodes can refer to functions as well, so these are blacklisted too.
http://gwt-code-reviews.appspot.com/169801
Patch by: cromwellian
Review by: me
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7701 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/js/JsDuplicateFunctionRemover.java b/dev/core/src/com/google/gwt/dev/js/JsDuplicateFunctionRemover.java
index 99bb1c5..93c8972 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsDuplicateFunctionRemover.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsDuplicateFunctionRemover.java
@@ -19,14 +19,20 @@
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.JsInvocation;
import com.google.gwt.dev.js.ast.JsModVisitor;
import com.google.gwt.dev.js.ast.JsName;
+import com.google.gwt.dev.js.ast.JsNameOf;
import com.google.gwt.dev.js.ast.JsNameRef;
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsVisitor;
+import com.google.gwt.dev.util.collect.IdentityHashSet;
import java.util.HashMap;
+import java.util.IdentityHashMap;
import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
/**
* Replace references to functions which have post-obfuscation duplicate bodies
@@ -37,8 +43,43 @@
private class DuplicateFunctionBodyRecorder extends JsVisitor {
- Map<String, JsName> uniqueBodies = new HashMap<String, JsName>();
- Map<JsName, JsName> duplicateOriginalMap = new HashMap<JsName, JsName>();
+ private final Set<JsName> dontReplace = new IdentityHashSet<JsName>();
+
+ private final Map<JsName, JsName> duplicateOriginalMap = new IdentityHashMap<JsName, JsName>();
+
+ private final Stack<JsNameRef> invocationQualifiers = new Stack<JsNameRef>();
+
+ private final Map<String, JsName> uniqueBodies = new HashMap<String, JsName>();
+
+ public DuplicateFunctionBodyRecorder() {
+ // Add sentinel to stop Stack.peek() from throwing exception.
+ invocationQualifiers.add(null);
+ }
+
+ @Override
+ public void endVisit(JsInvocation x, JsContext<JsExpression> ctx) {
+ if (x.getQualifier() instanceof JsNameRef) {
+ invocationQualifiers.pop();
+ }
+ }
+
+ @Override
+ public void endVisit(JsNameOf x, JsContext<JsExpression> ctx) {
+ dontReplace.add(x.getName());
+ }
+
+ @Override
+ public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
+ if (x != invocationQualifiers.peek()) {
+ if (x.getName() != null) {
+ dontReplace.add(x.getName());
+ }
+ }
+ }
+
+ public Set<JsName> getBlacklist() {
+ return dontReplace;
+ }
public Map<JsName, JsName> getDuplicateMap() {
return duplicateOriginalMap;
@@ -47,13 +88,9 @@
@Override
public boolean visit(JsFunction x, JsContext<JsExpression> ctx) {
/*
- * At this point, unpruned zero-arg functions with empty
- * bodies are Js constructor seed functions.
- * If constructors are ever inlined into seed functions, revisit this.
* Don't process anonymous functions.
*/
- if (x.getName() != null && x.getParameters().size() > 0 ||
- x.getBody().getStatements().size() > 0) {
+ if (x.getName() != null) {
String fnSource = x.toSource();
String body = fnSource.substring(fnSource.indexOf("("));
JsName original = uniqueBodies.get(body);
@@ -63,33 +100,51 @@
uniqueBodies.put(body, x.getName());
}
}
- return false;
+ return true;
+ }
+
+ @Override
+ public boolean visit(JsInvocation x, JsContext<JsExpression> ctx) {
+ if (x.getQualifier() instanceof JsNameRef) {
+ invocationQualifiers.push((JsNameRef) x.getQualifier());
+ }
+ return true;
}
}
private class ReplaceDuplicateInvocationNameRefs extends JsModVisitor {
- private Map<JsName, JsName> duplicateMap;
- public ReplaceDuplicateInvocationNameRefs(
- Map<JsName, JsName> duplicateMap) {
+ private final Set<JsName> blacklist;
+
+ private final Map<JsName, JsName> duplicateMap;
+
+ public ReplaceDuplicateInvocationNameRefs(Map<JsName, JsName> duplicateMap,
+ Set<JsName> blacklist) {
this.duplicateMap = duplicateMap;
+ this.blacklist = blacklist;
}
@Override
public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
JsName orig = duplicateMap.get(x.getName());
- if (orig != null && x.getName() != null &&
- x.getName().getEnclosing() == program.getScope()) {
+ if (orig != null && x.getName() != null
+ && x.getName().getEnclosing() == program.getScope()
+ && !blacklist.contains(x.getName()) && !blacklist.contains(orig)) {
ctx.replaceMe(orig.makeRef(x.getSourceInfo()));
}
}
}
+ // Needed for OptimizerTestBase
+ public static boolean exec(JsProgram program) {
+ return new JsDuplicateFunctionRemover(program).execImpl(program.getFragmentBlock(0));
+ }
+
public static boolean exec(JsProgram program, JsBlock fragment) {
return new JsDuplicateFunctionRemover(program).execImpl(fragment);
}
- private JsProgram program;
+ private final JsProgram program;
public JsDuplicateFunctionRemover(JsProgram program) {
this.program = program;
@@ -98,8 +153,8 @@
private boolean execImpl(JsBlock fragment) {
DuplicateFunctionBodyRecorder dfbr = new DuplicateFunctionBodyRecorder();
dfbr.accept(fragment);
- ReplaceDuplicateInvocationNameRefs rdup
- = new ReplaceDuplicateInvocationNameRefs(dfbr.getDuplicateMap());
+ ReplaceDuplicateInvocationNameRefs rdup = new ReplaceDuplicateInvocationNameRefs(
+ dfbr.getDuplicateMap(), dfbr.getBlacklist());
rdup.accept(fragment);
return rdup.didChange();
}
diff --git a/dev/core/test/com/google/gwt/dev/js/JsDuplicateFunctionRemoverTest.java b/dev/core/test/com/google/gwt/dev/js/JsDuplicateFunctionRemoverTest.java
new file mode 100644
index 0000000..9ce5659
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/js/JsDuplicateFunctionRemoverTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+/**
+ * Tests the JsStaticEval optimizer.
+ */
+public class JsDuplicateFunctionRemoverTest extends OptimizerTestBase {
+
+ public void testDontRemoveCtors() throws Exception {
+ // As fieldref qualifier
+ assertEquals("function a(){}\n;function b(){}\nb.prototype={};a();b();",
+ optimize("function a(){};function b(){} b.prototype={}; a(); b();"));
+ // As parameter
+ assertEquals(
+ "function defineSeed(a,b){}\n;function a(){}\n;function b(){}\ndefineSeed(a,b);a();b();",
+ optimize("function defineSeed(a,b){};function a(){};function b(){} defineSeed(a,b); a(); b();"));
+ }
+
+ public void testRemoveDuplicates() throws Exception {
+ assertEquals("function a(){}\n;a();a();",
+ optimize("function a(){};function b(){} a(); b();"));
+ }
+
+ private String optimize(String js) throws Exception {
+ return optimize(js, JsSymbolResolver.class,
+ JsDuplicateFunctionRemover.class, JsUnusedFunctionRemover.class);
+ }
+}
\ No newline at end of file