Revision: 10432
Author:   jbrosenb...@google.com
Date:     Fri Jul  8 10:47:24 2011
Log: Fix JsInliner, to properly handle name scope tracking after multi-level inlining. Was causing local variable name collisions after obfuscation.
Fixes issue 5936.

Review at http://gwt-code-reviews.appspot.com/1472803

http://code.google.com/p/google-web-toolkit/source/detail?r=10432

Modified:
 /trunk/dev/core/src/com/google/gwt/dev/js/JsInliner.java
 /trunk/dev/core/test/com/google/gwt/dev/js/JsInlinerTest.java

=======================================
--- /trunk/dev/core/src/com/google/gwt/dev/js/JsInliner.java Tue May 31 05:03:27 2011 +++ /trunk/dev/core/src/com/google/gwt/dev/js/JsInliner.java Fri Jul 8 10:47:24 2011
@@ -762,6 +762,47 @@
       return v.refersToName();
     }
   }
+
+  /**
+   * Collect names in a hoisted statement that are local to the original
+   * scope.  These names will need to be copied to the destination scope
+   * once the statement becomes hoisted.
+   */
+  private static class HoistedNameVisitor extends JsVisitor {
+    private final JsScope toScope;
+    private final JsScope fromScope;
+    private final List<JsName> hoistedNames;
+
+    public HoistedNameVisitor(JsScope toScope, JsScope fromScope) {
+      this.toScope = toScope;
+      this.fromScope = fromScope;
+      this.hoistedNames = new ArrayList<JsName>();
+    }
+
+    public List<JsName> getHoistedNames() {
+      return hoistedNames;
+    }
+
+    /*
+ * We need to hoist names that are only visible in fromScope, but not in
+     * toScope (i.e. we don't want to hoist names that are visible to both
+ * scopes, such as a global). Also, we don't want to hoist names that have a
+     * staticRef, which indicates a formal parameter, or a function name.
+     */
+    @Override
+    public boolean visit(JsNameRef nameRef, JsContext ctx) {
+      JsName name = nameRef.getName();
+      JsName fromScopeName = fromScope.findExistingName(name.getIdent());
+      JsName toScopeName = toScope.findExistingName(name.getIdent());
+      if (name.getStaticRef() == null
+          && name == fromScopeName
+          && name != toScopeName
+          && !hoistedNames.contains(name)) {
+        hoistedNames.add(name);
+      }
+      return true;
+    }
+  }

   /**
    * Collect all of the idents used in an AST node. The collector can be
@@ -1097,10 +1138,11 @@
         statements = Collections.emptyList();
       }

-      List<JsExpression> hoisted = new ArrayList<JsExpression>(
-          statements.size());
+ List<JsExpression> hoisted = new ArrayList<JsExpression>(statements.size()); JsExpression thisExpr = ((JsNameRef) x.getQualifier()).getQualifier();
-      List<JsName> localVariableNames = new ArrayList<JsName>();
+      HoistedNameVisitor hoistedNameVisitor =
+ new HoistedNameVisitor(callerFunction.getScope(), invokedFunction.getScope());
+
       boolean sawReturnStatement = false;

       for (JsStatement statement : statements) {
@@ -1128,10 +1170,16 @@
* distinct objects, it would not be possible to substitute different
          * JsNameRefs at different call sites.
          */
-        JsExpression h = hoistedExpression(statement, localVariableNames);
+        JsExpression h = hoistedExpression(statement);
         if (h == null) {
           return x;
         }
+
+        /*
+ * Visit the statement to find names that will be moved to the caller's
+         * scope from the invoked function.
+         */
+        hoistedNameVisitor.accept(statement);

         if (isReturnStatement(statement)) {
           sawReturnStatement = true;
@@ -1140,6 +1188,11 @@
           hoisted.add(h);
         }
       }
+
+      /*
+ * Get the referenced names that need to be copied to the caller's scope.
+       */
+      List<JsName> hoistedNames = hoistedNameVisitor.getHoistedNames();

       /*
* If the inlined method has no return statement, synthesize an undefined
@@ -1179,7 +1232,7 @@
       // Perform the name replacement
       NameRefReplacerVisitor v = new NameRefReplacerVisitor(thisExpr,
           x.getArguments(), invokedFunction.getParameters());
- for (ListIterator<JsName> nameIterator = localVariableNames.listIterator(); nameIterator.hasNext();) { + for (ListIterator<JsName> nameIterator = hoistedNames.listIterator(); nameIterator.hasNext();) {
         JsName name = nameIterator.next();

         /*
@@ -1210,7 +1263,7 @@
       op = v.accept(op);

       // Normalize any nested comma expressions that we may have generated.
-      op = (new CommaNormalizer(localVariableNames)).accept(op);
+      op = (new CommaNormalizer(hoistedNames)).accept(op);

       /*
* Compare the relative complexity of the original invocation versus the
@@ -1224,13 +1277,13 @@
         return x;
       }

- if (callerFunction == programFunction && localVariableNames.size() > 0) {
+      if (callerFunction == programFunction && hoistedNames.size() > 0) {
         // Don't add additional variables to the top-level program.
         return x;
       }

       // We've committed to the inlining, ensure the vars are created
-      newLocalVariableStack.peek().addAll(localVariableNames);
+      newLocalVariableStack.peek().addAll(hoistedNames);

       // update invocation counts according to this inlining
       invocationCountingVisitor.removeCountsFor(x);
@@ -1558,7 +1611,6 @@
   private static class RefersToNameVisitor extends JsVisitor {
     private final Collection<JsName> names;
     private boolean refersToName;
-    private boolean refersToUnbound;

     public RefersToNameVisitor(Collection<JsName> names) {
       this.names = names;
@@ -1568,9 +1620,7 @@
     public void endVisit(JsNameRef x, JsContext ctx) {
       JsName name = x.getName();

-      if (name == null) {
-        refersToUnbound = true;
-      } else {
+      if (name != null) {
         refersToName = refersToName || names.contains(name);
       }
     }
@@ -1578,10 +1628,6 @@
     public boolean refersToName() {
       return refersToName;
     }
-
-    public boolean refersToUnbound() {
-      return refersToUnbound;
-    }
   }

   /**
@@ -1701,7 +1747,7 @@

   /**
    * @param program
-   * @return
+   * @return stats
    */
   private static OptimizerStats execImpl(JsProgram program) {
     OptimizerStats stats = new OptimizerStats(NAME);
@@ -1783,15 +1829,12 @@
    * constructs a mutable copy of the expression that can be manipulated
    * at-will.
    *
-   * @param program the enclosing JsProgram
    * @param statement the statement from which to extract the expressions
-   * @param localVariableNames accumulates any local varables declared by
-   *          <code>statement</code>
* @return a JsExpression representing all expressions that would have been
    *         evaluated by the statement
    */
-  private static JsExpression hoistedExpression(JsStatement statement,
-      List<JsName> localVariableNames) {
+  private static JsExpression hoistedExpression(JsStatement statement) {
+
     JsExpression expression;
     if (statement instanceof JsExprStmt) {
       // Extract the expression
@@ -1815,9 +1858,6 @@
       expression = JsNullLiteral.INSTANCE;

       for (JsVar var : vars) {
-        // Record the locally-defined variable
-        localVariableNames.add(var.getName());
-
         // Extract the initialization expression
         JsExpression init = var.getInitExpr();
         if (init != null) {
@@ -1856,7 +1896,7 @@
   }

   /**
- * Given an expression, determine if it it is a JsNameRef that refers to a
+   * Given an expression, determine if it is a JsNameRef that refers to a
    * statically-defined JsFunction.
    */
   private static JsFunction isFunction(JsExpression e) {
=======================================
--- /trunk/dev/core/test/com/google/gwt/dev/js/JsInlinerTest.java Tue May 31 05:03:27 2011 +++ /trunk/dev/core/test/com/google/gwt/dev/js/JsInlinerTest.java Fri Jul 8 10:47:24 2011
@@ -48,22 +48,22 @@
   public void testInlineArrayLiterals() throws Exception {
     String input = "function a1(arg, x) { arg.x = x; return arg; }"
         + "function b1() { var x=a1([], 10); } b1();";
-    compare(input, input);
+    verifyNoChange(input);
   }

   public void testInlineFunctionLiterals() throws Exception {
     String input = "function a1(arg, x) { arg.x = x; return arg; }"
         + "function b1() { var x=a1(function (){}, 10); } b1();";
-    compare(input, input);
+    verifyNoChange(input);
     String input2 = "function a1(arg, x) { arg.x = x; return arg; }"
         + "function b1() { var x=a1(function blah(){}, 10); } b1();";
-    compare(input2, input2);
+    verifyNoChange(input2);
   }

   public void testInlineObjectLiterals() throws Exception {
     String input = "function a1(arg, x) { arg.x = x; return arg; }"
         + "function b1() { var x=a1({}, 10); } b1();";
-    compare(input, input);
+    verifyNoChange(input);
   }

   /**
@@ -81,7 +81,7 @@
         + "function c1() { return ex2? a1():c1(); } c1()";
String expected = "function a1() { return ex ? (ex2 ? a1() : c1()) : c1() }"
         + "function c1() { return ex2 ? a1() :c1(); } c1()";
-    compare(expected, input);
+    verifyOptimized(expected, input);
   }

   /**
@@ -102,7 +102,7 @@
     // bootstrap the program
     code.append("caller();");

-    compare(code.toString(), code.toString());
+    verifyNoChange(code.toString());
   }

   /**
@@ -127,7 +127,7 @@
expected.append("function caller() { var array; array[0] + (clinit(), 2); }");
     expected.append("caller();");

-    compare(expected.toString(), code.toString());
+    verifyOptimized(expected.toString(), code.toString());
   }

   /**
@@ -147,7 +147,7 @@
     // bootstrap the program
     code.append("caller();");

-    compare(code.toString(), code.toString());
+    verifyNoChange(code.toString());
   }

   /**
@@ -170,7 +170,7 @@
     // bootstrap the program
     code.append("caller();");

-    compare(code.toString(), code.toString());
+    verifyNoChange(code.toString());
   }

   /**
@@ -195,7 +195,7 @@
     expected.append("function clinit() { clinit = null; }");
     expected.append("function caller() {var y; return y=2,clinit(),3;}");
     expected.append("caller();");
-    compare(expected.toString(), code.toString());
+    verifyOptimized(expected.toString(), code.toString());
   }

   /**
@@ -218,7 +218,7 @@
     // bootstrap the program
     code.append("caller();");

-    compare(code.toString(), code.toString());
+    verifyNoChange(code.toString());
   }

   public void testSelfRecursion() throws Exception {
@@ -228,13 +228,58 @@
     String expected = "function a1() { return blah && bar && a1() }"
         + "function c() { a1() } c()";

-    compare(expected, input);
+    verifyOptimized(expected, input);
   }

-  private void compare(String expected, String input) throws Exception {
- input = optimize(input, JsSymbolResolver.class, FixStaticRefsVisitor.class,
+  /*
+   * This is inspired by issue 5936:
+   * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=5936
+   */
+ public void testPreserveNameScopeWithDoubleInliningAndObfuscation() throws Exception {
+    StringBuffer code = new StringBuffer();
+
+    code.append("function getA(){"
+                + "var s;"
+                + "s = getB();"
+                + "return s;"
+                + "}");
+
+    code.append("function getB(){"
+                + "var t;"
+                + "t = 't';"
+                + "t = t + '';"
+                + "return t;"
+                + "}");
+
+    code.append("function start(y){"
+                + "getA();"
+                + "if (y != 10) {$wnd.alert('y != 10');}"
+                + "}");
+
+    code.append("var x = 10; start(x);");
+
+    StringBuffer expected = new StringBuffer();
+ expected.append("function c(a){var b;b='t';if(a!=10){$wnd.alert('y != 10')}}");
+    expected.append("var d=10;c(d);");
+
+    verifyOptimizedObfuscated(expected.toString(), code.toString());
+  }
+
+  private void verifyNoChange(String input) throws Exception {
+    verifyOptimized(input, input);
+  }
+
+ private void verifyOptimized(String expected, String input) throws Exception { + String actual = optimize(input, JsSymbolResolver.class, FixStaticRefsVisitor.class,
         JsInliner.class, JsUnusedFunctionRemover.class);
-    expected = optimize(expected);
-    assertEquals(expected, input);
+    String expectedAfterParse = optimize(expected);
+    assertEquals(expectedAfterParse, actual);
+  }
+
+ private void verifyOptimizedObfuscated(String expected, String input) throws Exception { + String actual = optimize(input, JsSymbolResolver.class, FixStaticRefsVisitor.class, + JsInliner.class, JsUnusedFunctionRemover.class, JsObfuscateNamer.class);
+    String expectedAfterParse = optimize(expected);
+    assertEquals(expectedAfterParse, actual);
   }
 }

--
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Reply via email to