JChangeTrackingVisitor was missing some field modifications.

Fields were not considered modified if only their initializer
was modified.

Now we conservatively consider a static field modified if the class
initializer ($clinit)  for that that class was considered modified; and
similarly for instance fields and instance initializer.

Change-Id: Ie7c2897d01a77cdd789b50bfd20e500b8a361b32
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JClassType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JClassType.java
index 7d0e38d..4b94ea6 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JClassType.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JClassType.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.dev.jjs.SourceInfo;
 import com.google.gwt.dev.jjs.SourceOrigin;
+import com.google.gwt.dev.jjs.impl.GwtAstBuilder;
 
 import java.io.Serializable;
 
@@ -62,6 +63,19 @@
   }
 
   @Override
+  public final JMethod getInitMethod() {
+    assert getMethods().size() > 1;
+    JMethod init = this.getMethods().get(1);
+
+    if (!init.getName().equals(GwtAstBuilder.INIT_NAME)) {
+      // the init method was removed.
+      return null;
+    }
+
+    return init;
+  }
+
+  @Override
   public final JClassType getSuperClass() {
     return superClass;
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java
index c0df81f..01c201a 100755
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java
@@ -319,18 +319,10 @@
 
   /**
    * Returns the instance initializer ($init) method.
-   * Can only be called after making sure the class has an instance initializer method.
    *
    * @return The instance initializer method.
    */
-  public final JMethod getInitMethod() {
-    assert getMethods().size() > 1;
-    JMethod init = this.getMethods().get(1);
-
-    assert init != null;
-    assert init.getName().equals(GwtAstBuilder.INIT_NAME);
-    return init;
-  }
+  public abstract JMethod getInitMethod();
 
   @Override
   public String getJavahSignatureName() {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JInterfaceType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JInterfaceType.java
index 68992e2..0ad9d82 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JInterfaceType.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JInterfaceType.java
@@ -55,6 +55,11 @@
   }
 
   @Override
+  public final JMethod getInitMethod() {
+    return null;
+  }
+
+  @Override
   public JClassType getSuperClass() {
     return null;
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
index 2135760..061b708 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
@@ -276,6 +276,19 @@
     return isClinit;
   }
 
+  public static boolean isInit(JMethod method) {
+    JDeclaredType enclosingType = method.getEnclosingType();
+
+    if (method.isStatic()) {
+      // Hack, check the name.
+      return method.getName().equals(GwtAstBuilder.STATIC_INIT_NAME);
+    }
+
+    boolean isInit = enclosingType != null && method == enclosingType.getInitMethod();
+    assert !isInit || method.getName().equals(GwtAstBuilder.INIT_NAME);
+    return isInit;
+  }
+
   public static void serializeTypes(List<JDeclaredType> types, ObjectOutputStream stream)
       throws IOException {
     stream.writeObject(types);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
index e56beee..b602019 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
@@ -2031,9 +2031,11 @@
    * context).
    */
   public static OptimizerStats exec(JProgram program, OptimizerContext optimizerCtx) {
-    Iterable<JMethod> modifiedMethods =
+    Set<JMethod> affectedMethods =
         optimizerCtx.getModifiedMethodsSince(optimizerCtx.getLastStepFor(NAME));
-    OptimizerStats stats = new DeadCodeElimination(program).execImpl(modifiedMethods, optimizerCtx);
+    affectedMethods.addAll(optimizerCtx.getMethodsByReferencedFields(
+        optimizerCtx.getModifiedFieldsSince(optimizerCtx.getLastStepFor(NAME))));
+    OptimizerStats stats = new DeadCodeElimination(program).execImpl(affectedMethods, optimizerCtx);
     optimizerCtx.setLastStepFor(NAME, optimizerCtx.getOptimizationStep());
     optimizerCtx.incOptimizationStep();
     JavaAstVerifier.assertProgramIsConsistent(program);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JChangeTrackingVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JChangeTrackingVisitor.java
index 2294e5e..27fbff1 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JChangeTrackingVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JChangeTrackingVisitor.java
@@ -18,6 +18,7 @@
 import com.google.gwt.dev.jjs.ast.JField;
 import com.google.gwt.dev.jjs.ast.JMethod;
 import com.google.gwt.dev.jjs.ast.JModVisitor;
+import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.jjs.ast.JVariable;
 
 import java.util.Collection;
@@ -81,10 +82,24 @@
   @Override
   public final void endVisit(JMethod x, Context ctx) {
     exit(x, ctx);
-    if (methodModified) {
-      optimizerCtx.markModified(x);
-    }
     currentMethod = null;
+
+    if (!methodModified) {
+      return;
+    }
+
+    optimizerCtx.markModified(x);
+    if (JProgram.isClinit(x) || JProgram.isInit(x)) {
+      // Mark all class static fields as modified when a class clinit is modified to reflect
+      // that when inline declaration statements are modified the corresponding field must
+      // be considered modified.
+      for (JField potentiallyModifiedField: x.getEnclosingType().getFields()) {
+        if (potentiallyModifiedField.isStatic() && JProgram.isClinit(x)
+            || !potentiallyModifiedField.isStatic() && !JProgram.isClinit(x)) {
+          optimizerCtx.markModified(potentiallyModifiedField);
+        }
+      }
+    }
   }
 
   @Override
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JChangeTrackingVisitorTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JChangeTrackingVisitorTest.java
index 4ef8ecf..ac25619 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/JChangeTrackingVisitorTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JChangeTrackingVisitorTest.java
@@ -26,6 +26,13 @@
 import com.google.gwt.dev.jjs.ast.JPrimitiveType;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.jjs.ast.JVariable;
+import com.google.gwt.thirdparty.guava.common.base.Function;
+import com.google.gwt.thirdparty.guava.common.collect.FluentIterable;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
+import com.google.gwt.thirdparty.guava.common.collect.Sets;
+
+import java.util.Arrays;
+import java.util.Set;
 
 /**
  * Test for {@link JChangeTrackingVisitor}.
@@ -467,6 +474,8 @@
   public void testModificationTracking() throws Exception {
     addSnippetClassDecl("static class A {",
         "  public double field;",
+        "  public static int staticField = 1 < 2 ? 1 : 0;",
+        "  public int instanceField = 1 + 1;",
         "  public A(double f) { field = f; }",
         "  public void fun1 () { for(int i = 3; i < 4; i++) i = 8; }",
         "  public int fun2 (int a) { return a > 1 ? 1 : 0; }",
@@ -488,89 +497,103 @@
         new ReplaceConditionalExprWithItsThenExprVisitor(optimizerCtx);
     repalceConditionalExprVisitor.accept(program.getFromTypeMap("test.EntryPoint$A"));
     optimizerCtx.incOptimizationStep();
-    assertEquals(0, optimizerCtx.getModifiedFieldsSince(first).size());
 
-    assertEquals(3, optimizerCtx.getModifiedMethodsSince(first).size());
-    assertTrue(optimizerCtx.getModifiedMethodsSince(first).contains(
-        JJSTestBase.findMethod(program.getFromTypeMap("test.EntryPoint$A"), "fun2")));
-    assertTrue(optimizerCtx.getModifiedMethodsSince(first).contains(
-        JJSTestBase.findMethod(program.getFromTypeMap("test.EntryPoint$A"), "fun3")));
-    assertTrue(optimizerCtx.getModifiedMethodsSince(first).contains(
-        JJSTestBase.findMethod(program.getFromTypeMap("test.EntryPoint$A"), "fun5")));
+    Set<JMethod> modifiedMethodsInIteration1 =
+        ImmutableSet.copyOf(getMethods(program, "test.EntryPoint$A",
+            "fun2", "fun3", "fun5", GwtAstBuilder.CLINIT_NAME));
+    Set<JField> modifiedFieldsInIteration1 =
+        ImmutableSet.copyOf(getFields(program, "test.EntryPoint$A",
+            "staticField"));
+
+    assertEquals(modifiedFieldsInIteration1, optimizerCtx.getModifiedFieldsSince(first));
+    assertEquals(modifiedMethodsInIteration1, optimizerCtx.getModifiedMethodsSince(first));
 
     int second = optimizerCtx.getOptimizationStep();
     ReplaceAddOperationWithItsFirstOperandVisitor replaceAddOperationVisitor =
         new ReplaceAddOperationWithItsFirstOperandVisitor(optimizerCtx);
     replaceAddOperationVisitor.accept(program.getFromTypeMap("test.EntryPoint$A"));
     optimizerCtx.incOptimizationStep();
-    assertEquals(0, optimizerCtx.getModifiedFieldsSince(second).size());
 
-    assertEquals(2, optimizerCtx.getModifiedMethodsSince(second).size());
-    assertTrue(optimizerCtx.getModifiedMethodsSince(second).contains(
-        JJSTestBase.findMethod(program.getFromTypeMap("test.EntryPoint$A"), "fun4")));
-    assertTrue(optimizerCtx.getModifiedMethodsSince(second).contains(
-        JJSTestBase.findMethod(program.getFromTypeMap("test.EntryPoint$A"), "fun5")));
+    Set<JMethod> modifiedMethodsInIteration2 =
+        ImmutableSet.copyOf(getMethods(program, "test.EntryPoint$A",
+                "fun4", "fun5", GwtAstBuilder.INIT_NAME));
+    Set<JField> modifiedFieldsInIteration2 =
+        ImmutableSet.copyOf(getFields(program, "test.EntryPoint$A",
+            "instanceField", "field"));
 
-    assertEquals(0, optimizerCtx.getModifiedFieldsSince(first).size());
+    assertEquals(modifiedFieldsInIteration2, optimizerCtx.getModifiedFieldsSince(second));
+    assertEquals(modifiedMethodsInIteration2, optimizerCtx.getModifiedMethodsSince(second));
 
-    assertEquals(4, optimizerCtx.getModifiedMethodsSince(first).size());
-    assertTrue(optimizerCtx.getModifiedMethodsSince(first).contains(
-        JJSTestBase.findMethod(program.getFromTypeMap("test.EntryPoint$A"), "fun2")));
-    assertTrue(optimizerCtx.getModifiedMethodsSince(first).contains(
-        JJSTestBase.findMethod(program.getFromTypeMap("test.EntryPoint$A"), "fun3")));
-    assertTrue(optimizerCtx.getModifiedMethodsSince(first).contains(
-        JJSTestBase.findMethod(program.getFromTypeMap("test.EntryPoint$A"), "fun4")));
-    assertTrue(optimizerCtx.getModifiedMethodsSince(first).contains(
-        JJSTestBase.findMethod(program.getFromTypeMap("test.EntryPoint$A"), "fun5")));
+    assertEquals(Sets.union(modifiedFieldsInIteration1, modifiedFieldsInIteration2),
+        optimizerCtx.getModifiedFieldsSince(first));
+    assertEquals(Sets.union(modifiedMethodsInIteration1, modifiedMethodsInIteration2),
+        optimizerCtx.getModifiedMethodsSince(first));
 
     int third = optimizerCtx.getOptimizationStep();
     SetFieldOfIntToLongVisitor setFieldOfIntToLongVisitor =
         new SetFieldOfIntToLongVisitor(optimizerCtx);
     setFieldOfIntToLongVisitor.accept(program.getFromTypeMap("test.EntryPoint$B"));
     optimizerCtx.incOptimizationStep();
-    assertEquals(1, optimizerCtx.getModifiedFieldsSince(third).size());
-    assertTrue(optimizerCtx.getModifiedFieldsSince(third).contains(
-        JJSTestBase.findField(program.getFromTypeMap("test.EntryPoint$B"), "field1")));
 
-    assertEquals(0, optimizerCtx.getModifiedMethodsSince(third).size());
+    Set<JMethod> modifiedMethodsInIteration3 = ImmutableSet.of();
+    Set<JField> modifiedFieldsInIteration3 =
+        ImmutableSet.copyOf(getFields(program, "test.EntryPoint$B",
+            "field1"));
 
-    assertEquals(1, optimizerCtx.getModifiedFieldsSince(second).size());
-    assertTrue(optimizerCtx.getModifiedFieldsSince(second).contains(
-        JJSTestBase.findField(program.getFromTypeMap("test.EntryPoint$B"), "field1")));
+    assertEquals(modifiedFieldsInIteration3, optimizerCtx.getModifiedFieldsSince(third));
+    assertEquals(modifiedMethodsInIteration3, optimizerCtx.getModifiedMethodsSince(third));
 
-    assertEquals(2, optimizerCtx.getModifiedMethodsSince(second).size());
-    assertTrue(optimizerCtx.getModifiedMethodsSince(second).contains(
-        JJSTestBase.findMethod(program.getFromTypeMap("test.EntryPoint$A"), "fun4")));
-    assertTrue(optimizerCtx.getModifiedMethodsSince(second).contains(
-        JJSTestBase.findMethod(program.getFromTypeMap("test.EntryPoint$A"), "fun5")));
+    assertEquals(Sets.union(modifiedFieldsInIteration2, modifiedFieldsInIteration3),
+        optimizerCtx.getModifiedFieldsSince(second));
+    assertEquals(Sets.union(modifiedMethodsInIteration2, modifiedMethodsInIteration3),
+        optimizerCtx.getModifiedMethodsSince(second));
 
-    assertEquals(1, optimizerCtx.getModifiedFieldsSince(first).size());
-    assertTrue(optimizerCtx.getModifiedFieldsSince(first).contains(
-        JJSTestBase.findField(program.getFromTypeMap("test.EntryPoint$B"), "field1")));
+    assertEquals(Sets.union(Sets.union(
+            modifiedFieldsInIteration1, modifiedFieldsInIteration2), modifiedFieldsInIteration3),
+        optimizerCtx.getModifiedFieldsSince(first));
+    assertEquals(Sets.union(Sets.union(
+            modifiedMethodsInIteration1, modifiedMethodsInIteration2), modifiedMethodsInIteration3),
+        optimizerCtx.getModifiedMethodsSince(first));
 
-    assertEquals(4, optimizerCtx.getModifiedMethodsSince(first).size());
-    assertTrue(optimizerCtx.getModifiedMethodsSince(first).contains(
-        JJSTestBase.findMethod(program.getFromTypeMap("test.EntryPoint$A"), "fun2")));
-    assertTrue(optimizerCtx.getModifiedMethodsSince(first).contains(
-        JJSTestBase.findMethod(program.getFromTypeMap("test.EntryPoint$A"), "fun3")));
-    assertTrue(optimizerCtx.getModifiedMethodsSince(first).contains(
-        JJSTestBase.findMethod(program.getFromTypeMap("test.EntryPoint$A"), "fun4")));
-    assertTrue(optimizerCtx.getModifiedMethodsSince(first).contains(
-        JJSTestBase.findMethod(program.getFromTypeMap("test.EntryPoint$A"), "fun5")));
+    Set<JMethod> methodsDeletedInIteration4 = getMethods(program, "test.EntryPoint$A", "fun5");
 
     RemoveMethodsWithThreeParamsVisitor removeMethodsWithThreeParamsVisitor =
         new RemoveMethodsWithThreeParamsVisitor(optimizerCtx);
     removeMethodsWithThreeParamsVisitor.accept(program.getFromTypeMap("test.EntryPoint$A"));
-    assertEquals(3, optimizerCtx.getModifiedMethodsSince(first).size());
-    assertTrue(optimizerCtx.getModifiedMethodsSince(first).contains(
-        JJSTestBase.findMethod(program.getFromTypeMap("test.EntryPoint$A"), "fun2")));
-    assertTrue(optimizerCtx.getModifiedMethodsSince(first).contains(
-        JJSTestBase.findMethod(program.getFromTypeMap("test.EntryPoint$A"), "fun3")));
-    assertTrue(optimizerCtx.getModifiedMethodsSince(first).contains(
-        JJSTestBase.findMethod(program.getFromTypeMap("test.EntryPoint$A"), "fun4")));
 
+    assertEquals(Sets.difference(Sets.union(Sets.union(
+                modifiedMethodsInIteration1, modifiedMethodsInIteration2),
+                modifiedMethodsInIteration3),methodsDeletedInIteration4),
+        optimizerCtx.getModifiedMethodsSince(first));
+
+    Set<JField> fieldsDeletedInIteration4 = getFields(program, "test.EntryPoint$B", "field1");
     RemoveFieldsOfLongType removeFieldsOfLongType = new RemoveFieldsOfLongType(optimizerCtx);
     removeFieldsOfLongType.accept(program.getFromTypeMap("test.EntryPoint$B"));
-    assertEquals(0, optimizerCtx.getModifiedFieldsSince(first).size());
+
+    assertEquals(Sets.difference(Sets.union(Sets.union(
+                modifiedFieldsInIteration1, modifiedFieldsInIteration2),
+            modifiedFieldsInIteration3),fieldsDeletedInIteration4),
+        optimizerCtx.getModifiedFieldsSince(first));
+  }
+
+  private Set<JMethod> getMethods(
+      final JProgram program, final String clazz, String... methodNames) {
+    return FluentIterable.from(Arrays.asList(methodNames)).transform(
+        new Function<String, JMethod>() {
+          @Override
+          public JMethod apply(String methodName) {
+            return JJSTestBase.findMethod(program.getFromTypeMap(clazz), methodName);
+          }
+        }).toSet();
+  }
+
+  private Set<JField> getFields(final JProgram program, final String clazz, String... fieldNames) {
+    return FluentIterable.from(Arrays.asList(fieldNames)).transform(
+        new Function<String, JField>() {
+          @Override
+          public JField apply(String fieldName) {
+            return JJSTestBase.findField(program.getFromTypeMap(clazz), fieldName);
+          }
+        }).toSet();
   }
 }
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
index 7e53200..69bbc67 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
@@ -374,9 +374,9 @@
    */
   protected SourceLevel sourceLevel = SourceLevel.DEFAULT_SOURCE_LEVEL;
 
-  protected static void assertContainsAll(Iterable<String> expectedMethodSnippets,
-      Set<String> actualMethodSnippets) {
-    List<String> missing = FluentIterable.from(expectedMethodSnippets)
+  protected static <T> void assertContainsAll(Iterable<T> expectedMethodSnippets,
+      Set<T> actualMethodSnippets) {
+    List<T> missing = FluentIterable.from(expectedMethodSnippets)
         .filter(Predicates.not(Predicates.in(actualMethodSnippets)))
         .toList();
     assertTrue(missing + " not contained in " + actualMethodSnippets, missing.size() == 0);