Fix nullness analysis in TypeTightener.

(1) Makes the CanPotentiallySeeUninitializedFields analysis available
    in TypeTightener to fix an unsafe assumption when computing
    nullability.
(2) Fixes a bug in CanPotentiallySeeUninitializedFields:
    - instance initializer was incorrectly analyzed; the analysis
      assumed $$init was the instance initializer whereas it was
      the devirtualized instance initializer.
(3) Improves the strength of the analysis by explicitly checking whether
    a "this" reference (or its devirtualized version) is used for a
    polymorphic call, involved in the parameters to static calls or
    involved in an expression that may create an alias to it.
(4) Adds some missing optimizations related to nullability.

Change-Id: I7a14112af353d50c4d83a2a31f47b1138488586e
diff --git a/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java b/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java
index 88d1278..3f7f7c9 100644
--- a/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java
+++ b/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java
@@ -314,6 +314,7 @@
           "  public int hashCode() { return 0; }",
           "  public String replace(char c1, char c2) { return null; }",
           "  public boolean startsWith(String str) { return false; }",
+          "  public native String substring(int start, int len) /*-{ return \"\"; }-*/;",
           "  public String toLowerCase() { return null; }",
           "  public String toString() { return this; }",
           "  public static String valueOf(boolean b) { 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 a013234..9d80893 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
@@ -847,6 +847,16 @@
     return runtimeRebindRegistratorTypeName;
   }
 
+  public Collection<JType> getSubclasses(JType type) {
+    return Collections2.transform(typeOracle.getSubTypeNames(type.getName()),
+        new Function<String, JType>() {
+          public JType apply(String typeName) {
+            return getFromTypeMap(typeName);
+          }
+        }
+    );
+  }
+
   public JMethod getStaticImpl(JMethod method) {
     JMethod staticImpl = instanceToStaticMap.get(method);
     assert staticImpl == null || staticImpl.getEnclosingType().getMethods().contains(staticImpl);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JThisRef.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JThisRef.java
index 5c4d9d6..0778d07 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JThisRef.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JThisRef.java
@@ -18,7 +18,7 @@
 import com.google.gwt.dev.jjs.SourceInfo;
 
 /**
- * Java method this expression.
+ * Java method this (or super) expression.
  */
 public class JThisRef extends JExpression {
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/js/JMultiExpression.java b/dev/core/src/com/google/gwt/dev/jjs/ast/js/JMultiExpression.java
index da4b558..4a8d6a5 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/js/JMultiExpression.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/js/JMultiExpression.java
@@ -24,6 +24,7 @@
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
 
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
@@ -45,7 +46,7 @@
   /**
    * Construct a multi expression containing {@code expressions}.
    */
-  public JMultiExpression(SourceInfo info, List<JExpression> expressions) {
+  public JMultiExpression(SourceInfo info, Collection<JExpression> expressions) {
     super(info);
     this.expressions.addAll(expressions);
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ComputePotentiallyObservableUninitializedValues.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ComputePotentiallyObservableUninitializedValues.java
new file mode 100644
index 0000000..556415e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ComputePotentiallyObservableUninitializedValues.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2014 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.jjs.impl;
+
+import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JClassType;
+import com.google.gwt.dev.jjs.ast.JConstructor;
+import com.google.gwt.dev.jjs.ast.JField;
+import com.google.gwt.dev.jjs.ast.JFieldRef;
+import com.google.gwt.dev.jjs.ast.JInterfaceType;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JMethodCall;
+import com.google.gwt.dev.jjs.ast.JParameter;
+import com.google.gwt.dev.jjs.ast.JParameterRef;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.ast.JThisRef;
+import com.google.gwt.dev.jjs.ast.JType;
+import com.google.gwt.dev.jjs.ast.JVisitor;
+import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
+import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
+import com.google.gwt.thirdparty.guava.common.base.Predicate;
+import com.google.gwt.thirdparty.guava.common.collect.Sets;
+
+import java.util.Set;
+
+/**
+ * Determines conservatively which classes can potentially see uninitialized values of their
+ * subclasses' fields.
+ * <p>
+ * This simple conservative analysis relies on the fact that when: <ul>
+ *   <li> (1) there are no virtual calls on "this" in any of the initialization methods
+ *        (constructors, init) of all the superclasses, and </li>
+ *   <li> (2) "this" does not escape through a parameter to other methods, and </li>
+ *   <li>(3) "this" is not aliased (stored into another field, variable or array)</li>
+ * </ul>
+ * then uninitialized values for subclass fields can never be seen.
+ * <p>
+ * This analysis is used to strengthen the nullness analysis performed by {@link TypeTightener} and
+ * to hoist initialization of instance fields to the prototype in {@link GenerateJavaScriptAST}.
+ */
+public class ComputePotentiallyObservableUninitializedValues {
+
+  private static final String NAME =
+      ComputePotentiallyObservableUninitializedValues.class.getSimpleName();
+  private final JProgram program;
+  private final Set<JType> classesWhoseFieldsCanBeObservedUninitialized = Sets.newHashSet();
+
+  private ComputePotentiallyObservableUninitializedValues(JProgram program) {
+    this.program = program;
+  }
+
+  /**
+   * Perform the analysis to compute which fields can be observed uninitialized.
+   */
+  public static Predicate<JField> analyze(JProgram program) {
+    return new ComputePotentiallyObservableUninitializedValues(program).analyzeImpl();
+  }
+
+  private Predicate<JField> analyzeImpl() {
+    SpeedTracerLogger.Event optimizeEvent =
+        SpeedTracerLogger.start(CompilerEventType.OPTIMIZE, "optimizer", NAME);
+    CanObserveSubclassUninitializedFieldsVisitor visitor =
+        new CanObserveSubclassUninitializedFieldsVisitor();
+    visitor.accept(program);
+    Set<JType> classesThatCanPotentiallyObserveUninitializedSubclassFields =
+        visitor.classesThatCanPotentiallyObserveUninitializedSubclassFields;
+
+    for (JType type : classesThatCanPotentiallyObserveUninitializedSubclassFields) {
+      if (classesWhoseFieldsCanBeObservedUninitialized.contains(type)) {
+        // Already processed.
+        continue;
+      }
+      classesWhoseFieldsCanBeObservedUninitialized.addAll(program.getSubclasses(type));
+    }
+
+    optimizeEvent.end();
+
+    return new Predicate<JField>() {
+      @Override
+      public boolean apply(JField field) {
+        return isUninitializedValueObservable(field);
+      }
+    };
+  }
+
+  private boolean isUninitializedValueObservable(JField x) {
+    if (x.getLiteralInitializer() != null && (x.isFinal() || x.isStatic())) {
+      // Static and final fields that are initialized to a (value) literal can not be observed in
+      // uninitialized state.
+      return false;
+    }
+
+    if (x.isStatic()) {
+      // Static fields can potentially be observed uninitialized if clinit dependencies are
+      // cyclical.
+      return true;
+    }
+
+    return classesWhoseFieldsCanBeObservedUninitialized.contains(x.getEnclosingType());
+  }
+
+  private class CanObserveSubclassUninitializedFieldsVisitor extends JVisitor {
+    private JClassType currentClass;
+    private JParameter devirtualizedThis;
+    private Set<JType> classesThatCanPotentiallyObserveUninitializedSubclassFields =
+        Sets.newHashSet();
+
+    @Override
+    public void endVisit(JClassType x, Context ctx) {
+      assert currentClass == x;
+      currentClass = null;
+    }
+
+    @Override
+    public void endVisit(JConstructor x, Context ctx) {
+      assert currentClass == x.getEnclosingType();
+      assert devirtualizedThis == null;
+    }
+
+    @Override
+    public void endVisit(JMethod x, Context ctx) {
+      assert currentClass == x.getEnclosingType();
+      devirtualizedThis = null;
+    }
+
+    @Override
+    public void endVisit(JThisRef x, Context ctx) {
+      // Seen a reference to "this" that can potentially escape or be used as instance in a
+      // method call.
+      classesThatCanPotentiallyObserveUninitializedSubclassFields.add(currentClass);
+    }
+
+    public void endVisit(JParameterRef x, Context ctx) {
+      if (x.getParameter() == devirtualizedThis) {
+        // Seen a reference to devirtualized "this" that can potentially escape or be used as
+        // instance in a method call.
+        classesThatCanPotentiallyObserveUninitializedSubclassFields.add(currentClass);
+      }
+    }
+
+    @Override
+    public boolean visit(JClassType x, Context ctx) {
+      assert currentClass == null;
+      currentClass = x;
+      return true;
+    }
+
+    @Override
+    public boolean visit(JConstructor x, Context ctx) {
+      // Only look at constructor bodies.
+      assert currentClass == x.getEnclosingType();
+      return true;
+    }
+
+    @Override
+    public boolean visit(JFieldRef x, Context ctx) {
+      if (isFieldReferenceThroughThis(x) &&  isFieldDeclaredInCurrentClassOrSuper(x)) {
+        // Accessing fields through this (or devirtualized this) from the current class or
+        // any super is ok, no further checks are needed.
+        // A subclass field ref can leak into superclass methods when optimizations are enabled.
+        return false;
+      }
+      return true;
+    }
+
+    @Override
+    public boolean visit(JInterfaceType x, Context ctx) {
+      // No need to examine interfaces.
+      return false;
+    }
+
+    @Override
+    public boolean visit(JMethod x, Context ctx) {
+      assert currentClass == x.getEnclosingType();
+      if (isInitMethod(x)) {
+        return true;
+      }
+
+      if (isDevirtualizedInitMethod(x) && x.getParams().size() > 0
+          && x.getParams().get(0).getType() == currentClass) {
+        devirtualizedThis = x.getParams().get(0);
+      }
+      // Do not explore the method body if it is not a constructor or the instance initializer.
+      return false;
+    }
+
+    @Override
+    public boolean visit(JMethodCall x, Context ctx) {
+      // This is a method call inside a constructor.
+      assert currentClass != null;
+      // Calls to this/super constructors and instance initializers are okay, as they will also
+      // get examined.
+      if ((x.getTarget().isConstructor()) && x.getInstance() instanceof JThisRef ||
+          isInitMethod(x.getTarget())) {
+        // Make sure "this" references do not escape through parameters
+        accept(x.getArgs());
+        return false;
+      }
+
+      // The instance initializers are always devirtualized, handle them specially.
+      if (isDevirtualizedInitMethod(x.getTarget()) && x.getArgs().size() > 0 &&
+          x.getArgs().get(0) instanceof JThisRef) {
+        // Make sure "this" references do not escape through parameters other than the first.
+        accept(x.getArgs().subList(1, x.getArgs().size()));
+        return false;
+      }
+
+      if (!x.getTarget().isStatic() && !x.getTarget().isFinal() &&
+          x.getInstance() instanceof JThisRef) {
+        // This is polymorphic method call on this, hence it is potentially unsafe.
+        classesThatCanPotentiallyObserveUninitializedSubclassFields.add(currentClass);
+        return false;
+      }
+
+      // This is a static call, if there is no "this" references in its parameters then it might
+      // be ok.
+      return true;
+    }
+
+    private boolean isDevirtualizedInitMethod(JMethod method) {
+      return method.isStatic() && method.getName().equals("$$init") &&
+          method.getEnclosingType() == currentClass;
+    }
+
+    private boolean isInitMethod(JMethod method) {
+      return !method.isStatic() && method.getName().equals("$init") &&
+          method.getEnclosingType() == currentClass;
+    }
+
+    private boolean isFieldReferenceThroughThis(JFieldRef x) {
+      return x.getInstance() instanceof JThisRef || x.getInstance() instanceof JParameterRef &&
+          ((JParameterRef) x.getInstance()).getParameter() == devirtualizedThis;
+    }
+
+    private boolean isFieldDeclaredInCurrentClassOrSuper(JFieldRef x) {
+      JClassType enclosingClass = (JClassType) x.getField().getEnclosingType();
+      return currentClass == enclosingClass ||
+          program.typeOracle.isSuperClass(enclosingClass, currentClass);
+    }
+  }
+}
\ No newline at end of file
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 2b5afe5..b5b0779 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
@@ -71,8 +71,6 @@
 import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
-import com.google.gwt.thirdparty.guava.common.base.Predicate;
-import com.google.gwt.thirdparty.guava.common.collect.Iterables;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
 
@@ -149,11 +147,48 @@
       JBinaryOperator op = x.getOp();
       JExpression lhs = x.getLhs();
       JExpression rhs = x.getRhs();
+
+      // If either parameter is a multiexpression, restructure if possible.
+      if (isNonEmptyMultiExpression(lhs)) {
+        // Push the operation inside the multiexpression. This exposes other optimization
+        // opportunities for latter passes e.g:
+        //
+        // (a(), b(), 1) + 2 ==> (a(), b(), 1 + 2) ==> (a(), b(), 3)
+        //
+        // There is no need to consider the symmetric case as it requires that all expression in the
+        // rhs (except the last one) to be side effect free, in which case they will end up being
+        // removed anyway.
+
+        List<JExpression> expressions = ((JMultiExpression) lhs).getExpressions();
+        JMultiExpression result = new JMultiExpression(lhs.getSourceInfo(),
+            expressions.subList(0, expressions.size() - 1));
+        result.addExpressions(new JBinaryOperation(x.getSourceInfo(), x.getType(), x.getOp(),
+            expressions.get(expressions.size() - 1), rhs));
+        ctx.replaceMe(result);
+        return;
+      }
+
+      if (isNonEmptyMultiExpression(rhs) && lhs instanceof JValueLiteral) {
+        // Push the operation inside the multiexpression if the lhs is a value literal.
+        // This exposes other optimization opportunities for latter passes e.g:
+        //
+        // 2 + (a(), b(), 1) ==> (a(), b(), 2 + 1) ==> (a(), b(), 3)
+        //
+        List<JExpression> expressions = ((JMultiExpression) rhs).getExpressions();
+        JMultiExpression result = new JMultiExpression(rhs.getSourceInfo(),
+            expressions.subList(0, expressions.size() - 1));
+        result.addExpressions(new JBinaryOperation(x.getSourceInfo(), x.getType(), x.getOp(),
+            lhs, expressions.get(expressions.size() - 1)));
+        ctx.replaceMe(result);
+        return;
+      }
+
       if ((lhs instanceof JValueLiteral) && (rhs instanceof JValueLiteral)) {
         if (evalOpOnLiterals(op, (JValueLiteral) lhs, (JValueLiteral) rhs, ctx)) {
           return;
         }
       }
+
       switch (op) {
         case AND:
           maybeReplaceMe(x, Simplifier.and(x), ctx);
@@ -200,6 +235,11 @@
       }
     }
 
+    private boolean isNonEmptyMultiExpression(JExpression expression) {
+      return expression instanceof JMultiExpression &&
+          !((JMultiExpression) expression).getExpressions().isEmpty();
+    }
+
     /**
      * Prune dead statements and empty blocks.
      */
@@ -386,7 +426,6 @@
      */
     @Override
     public void endVisit(JInstanceOf x, Context ctx) {
-
       if (ignoringExpressionOutput.contains(x)) {
         ctx.replaceMe(x.getExpr());
         ignoringExpressionOutput.remove(x);
@@ -514,7 +553,7 @@
       super.endVisit(x, ctx);
       /*
        * If the result of a new operation is ignored, we can remove it, provided
-       * / it has no side effects.
+       * it has no side effects.
        */
       if (ignoringExpressionOutput.contains(x)) {
         if (!x.getTarget().isEmpty()) {
@@ -549,6 +588,17 @@
       if (x.getOp().isModifying()) {
         lvalues.remove(x.getArg());
       }
+
+      if (isNonEmptyMultiExpression(x.getArg())) {
+        List<JExpression> expressions = ((JMultiExpression) x.getArg()).getExpressions();
+        JMultiExpression result = new JMultiExpression(x.getArg().getSourceInfo(),
+            expressions.subList(0, expressions.size() - 1));
+        result.addExpressions(new JPostfixOperation(x.getSourceInfo(), x.getOp(),
+            expressions.get(expressions.size() - 1)));
+        ctx.replaceMe(result);
+        return;
+      }
+
       if (ignoringExpressionOutput.contains(x)) {
         JPrefixOperation newOp = new JPrefixOperation(x.getSourceInfo(), x.getOp(), x.getArg());
         ctx.replaceMe(newOp);
@@ -563,11 +613,24 @@
       if (x.getOp().isModifying()) {
         lvalues.remove(x.getArg());
       }
+
+      // If the argument is a multiexpression restructure it if possible.
+      if (isNonEmptyMultiExpression(x.getArg())) {
+        List<JExpression> expressions = ((JMultiExpression) x.getArg()).getExpressions();
+        JMultiExpression result = new JMultiExpression(x.getArg().getSourceInfo(),
+            expressions.subList(0, expressions.size() - 1));
+        result.addExpressions(new JPrefixOperation(x.getSourceInfo(), x.getOp(),
+            expressions.get(expressions.size() - 1)));
+        ctx.replaceMe(result);
+        return;
+      }
+
       if (x.getArg() instanceof JValueLiteral) {
         if (evalOpOnLiteral(x.getOp(), (JValueLiteral) x.getArg(), ctx)) {
           return;
         }
       }
+
       if (x.getOp() == JUnaryOperator.NOT) {
         maybeReplaceMe(x, Simplifier.not(x), ctx);
         return;
@@ -1522,26 +1585,6 @@
       return AnalysisResult.UNKNOWN;
     }
 
-    private JExpression simplifyNonSideEffects(JExpression result,
-        JExpression... evaluateIfSideEffects) {
-
-      List<JExpression> expressionsWithSideEffects = Lists.newArrayList(Iterables.filter(
-          Arrays.asList(evaluateIfSideEffects), new Predicate<JExpression>() {
-        @Override
-        public boolean apply(JExpression expression) {
-          return expression.hasSideEffects();
-        }
-      }));
-
-      if (expressionsWithSideEffects.isEmpty()) {
-        return result;
-      }
-
-      expressionsWithSideEffects.add(result);
-      return new JMultiExpression(expressionsWithSideEffects.get(0).getSourceInfo(),
-          expressionsWithSideEffects);
-    }
-
     /**
      * Simplify <code>lhs == rhs</code>. If <code>negate</code> is true, then
      * it's actually static evaluation of <code>lhs != rhs</code>.
@@ -1550,8 +1593,8 @@
       // simplify: null == null -> true
       AnalysisResult analysisResult = staticallyEvaluateEq(lhs, rhs);
       if (analysisResult != AnalysisResult.UNKNOWN) {
-        ctx.replaceMe(simplifyNonSideEffects(
-            program.getLiteralBoolean(negate ^ (analysisResult == AnalysisResult.TRUE)), lhs, rhs));
+        ctx.replaceMe(JjsUtils.createOptimizedMultiExpression(
+            lhs, rhs, program.getLiteralBoolean(negate ^ (analysisResult == AnalysisResult.TRUE))));
         return;
       }
 
@@ -1971,5 +2014,5 @@
     return stats;
   }
 
-  private enum AnalysisResult { TRUE, FALSE, UNKNOWN };
+  private enum AnalysisResult { TRUE, FALSE, UNKNOWN }
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index acc97c0..3c05506 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -170,6 +170,7 @@
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
 import com.google.gwt.thirdparty.guava.common.base.Function;
+import com.google.gwt.thirdparty.guava.common.base.Predicate;
 import com.google.gwt.thirdparty.guava.common.base.Predicates;
 import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
 import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
@@ -2949,16 +2950,8 @@
         // we can definitely initialize at top-scope, as JVM does so as well
         return true;
       }
-      // if the superclass can observe the field, we need to leave the default value
-      JDeclaredType current = x.getEnclosingType().getSuperClass();
-      while (current != null) {
-        if (canObserveSubclassFields.contains(current)) {
-          return false;
-        }
-        current = current.getSuperClass();
-      }
-      // should be safe to initialize at top-scope, as no one can observe the difference
-      return true;
+
+      return !uninitializedValuePotentiallyObservable.apply(x);
     }
 
     // Keep track of a translation stack.
@@ -3041,70 +3034,6 @@
     }
   }
 
-  /**
-   * Determines which classes can potentially see uninitialized values of their subclasses' fields.
-   *
-   * If a class can not observe subclass uninitialized fields then the initialization of those could
-   * be hoisted to the prototype.
-   */
-  private class CanObserveSubclassUninitializedFieldsVisitor extends JVisitor {
-    private JDeclaredType currentClass;
-
-    @Override
-    public boolean visit(JConstructor x, Context ctx) {
-      // Only look at constructor bodies.
-      assert currentClass == null;
-      currentClass = x.getEnclosingType();
-      return true;
-    }
-
-    @Override
-    public void endVisit(JConstructor x, Context ctx) {
-      currentClass = null;
-    }
-
-    @Override
-    public boolean visit(JMethod x, Context ctx) {
-      if (x.getName().equals("$$init")) {
-        assert currentClass == null;
-        currentClass = x.getEnclosingType();
-        return true;
-      }
-      // Do not traverse the method body if it is not a constructor.
-      return false;
-    }
-
-    @Override
-    public void endVisit(JMethod x, Context ctx) {
-      currentClass = null;
-    }
-
-    @Override
-    public void endVisit(JMethodCall x, Context ctx) {
-      // This is a method call inside a constructor.
-      assert currentClass != null;
-      // Calls to this/super constructors are okay, as they will also get examined
-      if (x.getTarget().isConstructor() && x.getInstance() instanceof JThisRef) {
-        return;
-      }
-      // Calls to the instance initializer are okay, as execution will not escape it
-      if (x.getTarget().getName().equals("$$init")) {
-        return;
-      }
-      // Calls to static methods with no arguments are safe, because they have no
-      // way to trace back into our new instance.
-      if (x.getTarget().isStatic() && x.getTarget().getOriginalParamTypes().size() == 0) {
-        return;
-      }
-      // Technically we could get fancier about trying to track the "this" reference
-      // through other variable assignments, field assignments, and methods calls, to
-      // see if it calls a polymorphic method against ourself (which would let subclasses
-      // observe their fields), but that gets tricky, so we'll use the above heuristics
-      // for now.
-      canObserveSubclassFields.add(currentClass);
-    }
-  }
-
   private class RecordJSInlinableMethods extends JVisitor {
 
     private JMethod currentMethod;
@@ -3314,7 +3243,7 @@
    * Classes that could potentially see uninitialized values for fields that are initialized in the
    * declaration.
    */
-  private final Set<JDeclaredType> canObserveSubclassFields = Sets.newHashSet();
+  private Predicate<JField> uninitializedValuePotentiallyObservable;
 
   private final Map<JAbstractMethodBody, JsFunction> methodBodyMap = Maps.newIdentityHashMap();
   private final Map<HasName, JsName> names = Maps.newIdentityHashMap();
@@ -3579,18 +3508,10 @@
     }
   }
 
-  private void assumeAllClassesCanObserveUninitializedSubclassFields(JProgram program) {
-    canObserveSubclassFields.addAll(program.getDeclaredTypes());
-  }
-
   private Pair<JavaToJavaScriptMap, Set<JsNode>> execImpl() {
     new FixNameClashesVisitor().accept(program);
-    if (optimize) {
-      new CanObserveSubclassUninitializedFieldsVisitor().accept(program);
-    } else {
-      // Assume the worst case.
-      assumeAllClassesCanObserveUninitializedSubclassFields(program);
-    }
+    uninitializedValuePotentiallyObservable = optimize ?
+        ComputePotentiallyObservableUninitializedValues.analyze(program) : Predicates.<JField>alwaysTrue();
     new FindNameOfTargets().accept(program);
     new SortVisitor().accept(program);
     if (hasWholeWorldKnowledge) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JjsUtils.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JjsUtils.java
index 14f3f5e..feeba91 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JjsUtils.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JjsUtils.java
@@ -16,6 +16,7 @@
 package com.google.gwt.dev.jjs.impl;
 
 import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.SourceOrigin;
 import com.google.gwt.dev.jjs.ast.JBooleanLiteral;
 import com.google.gwt.dev.jjs.ast.JCharLiteral;
 import com.google.gwt.dev.jjs.ast.JDoubleLiteral;
@@ -26,6 +27,7 @@
 import com.google.gwt.dev.jjs.ast.JLongLiteral;
 import com.google.gwt.dev.jjs.ast.JNullLiteral;
 import com.google.gwt.dev.jjs.ast.JStringLiteral;
+import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
 import com.google.gwt.dev.js.ast.JsBooleanLiteral;
 import com.google.gwt.dev.js.ast.JsExpression;
 import com.google.gwt.dev.js.ast.JsLiteral;
@@ -35,15 +37,72 @@
 import com.google.gwt.dev.js.ast.JsObjectLiteral;
 import com.google.gwt.dev.js.ast.JsStringLiteral;
 import com.google.gwt.lang.LongLib;
+import com.google.gwt.thirdparty.guava.common.base.Predicate;
+import com.google.gwt.thirdparty.guava.common.base.Predicates;
+import com.google.gwt.thirdparty.guava.common.collect.Collections2;
 import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
 
+import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 
 /**
- * Translates Java literals into JavaScript literals.
+ * General utilities related to Java AST manipulation.
  */
 public class JjsUtils {
 
+  /**
+   * Creates a multi expression from a list of expressions, removing expressions that do
+   * not have side effects if possible.
+   */
+  public static JExpression createOptimizedMultiExpression(JExpression... expressions) {
+    return createOptimizedMultiExpression(false, Arrays.asList(expressions));
+  }
+
+  /**
+   * Creates a multi expression from a list of expressions, removing expressions that do
+   * not have side effects if possible.
+   */
+  public static JExpression createOptimizedMultiExpression(boolean ignoringResult,
+      List<JExpression> expressions) {
+
+    int numberOfExpressions = expressions.size();
+    JExpression result = expressions.get(numberOfExpressions - 1);
+
+    numberOfExpressions = expressions.size();
+    if (numberOfExpressions == 0) {
+      return new JMultiExpression(SourceOrigin.UNKNOWN);
+    }
+
+    expressions =  Lists.newArrayList(Collections2.filter(
+        expressions.subList(0, numberOfExpressions - 1),
+        Predicates.and(
+            Predicates.notNull(),
+            new Predicate<JExpression>() {
+              @Override
+              public boolean apply(JExpression expression) {
+                return expression.hasSideEffects();
+              }
+            })));
+
+    if (result != null && (!ignoringResult || result.hasSideEffects())) {
+      expressions.add(result);
+    }
+
+    if (expressions.size() == 1) {
+      // Do not create a multi expression if it consists only of the result.
+      return expressions.iterator().next();
+    }
+
+    SourceInfo info = expressions.size() > 0 ? expressions.get(0).getSourceInfo() :
+        SourceOrigin.UNKNOWN;
+    return new JMultiExpression(info, expressions);
+  }
+
+  /**
+   * Translates a Java literal into a JavaScript literal.
+   */
   public static JsLiteral translateLiteral(JLiteral literal) {
     return translatorByLiteralClass.get(literal.getClass()).translate(literal);
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
index 819a35f..de4aca5 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
@@ -37,9 +37,11 @@
 import com.google.gwt.dev.jjs.ast.JType;
 import com.google.gwt.dev.jjs.ast.JVisitor;
 import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
+import com.google.gwt.dev.util.collect.Stack;
 import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
 
 import java.util.LinkedHashSet;
@@ -84,7 +86,12 @@
      * inlined before might become inlinable.
      */
     private final Set<JMethod> cannotInline = Sets.newHashSet();
-    private JExpression ignoringReturnValueFor;
+    private final Stack<JExpression> expressionsWhoseValuesAreIgnored = Stack.create();
+
+    @Override
+    public void endVisit(JExpressionStatement x, Context ctx) {
+      expressionsWhoseValuesAreIgnored.pop();
+    }
 
     @Override
     public void endVisit(JMethod x, Context ctx) {
@@ -104,29 +111,36 @@
         return;
       }
 
-      if (!tryInlineMethodCall(x, ctx)) {
+      if (tryInlineMethodCall(x, ctx) == InlineResult.BLACKLIST) {
         // Do not try to inline this method again
         cannotInline.add(method);
       }
     }
 
-    private boolean tryInlineMethodCall(JMethodCall x, Context ctx) {
+    @Override
+    public void endVisit(JMultiExpression x, Context ctx) {
+      for (int i = 0; i < x.getExpressions().size() - 1; i++) {
+        expressionsWhoseValuesAreIgnored.pop();
+      }
+    }
+
+    private InlineResult tryInlineMethodCall(JMethodCall x, Context ctx) {
       JMethod method = x.getTarget();
       if (program.isJsTypePrototype(method.getEnclosingType())) {
          /*
           * Don't inline calls to JsType Prototype methods, since these are merely stubs to
           * preserve super calls to JS prototype methods.
           */
-        return false;
+        return InlineResult.BLACKLIST;
       }
 
       if (!method.isStatic() || method.isNative()) {
         // Only inline static methods that are not native.
-        return false;
+        return InlineResult.BLACKLIST;
       }
 
       if (!program.isInliningAllowed(method)) {
-        return false;
+        return InlineResult.BLACKLIST;
       }
 
       JMethodBody body = (JMethodBody) method.getBody();
@@ -135,24 +149,23 @@
       if (method.getEnclosingType() != null
          && method.getEnclosingType().getClinitMethod() == method && !stmts.isEmpty()) {
           // clinit() calls cannot be inlined unless they are empty
-        return false;
+        return InlineResult.BLACKLIST;
       }
 
       if (!body.getLocals().isEmpty()) {
           // methods with local variables cannot be inlined
-        return false;
+        return InlineResult.BLACKLIST;
       }
 
       // try to inline
-      JMultiExpression multi = createMultiExpressionFromBody(body, ignoringReturnValueFor == x);
-      if (multi == null || !tryInlineExpression(x, ctx, multi)) {
+      List<JExpression> expressions = extractExpressionsFromBody(body);
+      if (expressions == null) {
         // If it will never be possible to inline the method, add it to a
         // blacklist
-        return false;
+        return InlineResult.BLACKLIST;
       }
 
-      // Successfully inlined.
-      return true;
+      return tryInlineBody(x, ctx, expressions, expressionsWhoseValuesAreIgnored.contains(x));
     }
 
     @Override
@@ -162,7 +175,7 @@
 
     @Override
     public boolean visit(JExpressionStatement x, Context ctx) {
-      ignoringReturnValueFor = x.getExpr();
+      expressionsWhoseValuesAreIgnored.push(x.getExpr());
       return true;
     }
 
@@ -186,6 +199,14 @@
       return true;
     }
 
+    @Override
+    public boolean visit(JMultiExpression x, Context ctx) {
+      for (int i = 0; i < x.getExpressions().size() - 1; i++) {
+        expressionsWhoseValuesAreIgnored.push(x.getExpression(i));
+      }
+      return true;
+    }
+
     private JMethodCall createClinitCall(JMethodCall x) {
       JDeclaredType targetType = x.getTarget().getEnclosingType().getClinitTarget();
       if (!currentMethod.getEnclosingType().checkClinitTo(targetType)) {
@@ -213,27 +234,6 @@
     }
 
     /**
-     * Creates a multi expression for evaluating a method call instance and
-     * possible clinit. This is a precursor for inlining the remainder of a
-     * method.
-     */
-    private JMultiExpression createMultiExpressionForInstanceAndClinit(JMethodCall x) {
-      JMultiExpression multi = new JMultiExpression(x.getSourceInfo());
-
-      // Any instance expression goes first (this can happen even with statics).
-      if (x.getInstance() != null) {
-        multi.addExpressions(x.getInstance());
-      }
-
-      // If we need a clinit call, add it first
-      JMethodCall clinit = createClinitCall(x);
-      if (clinit != null) {
-        multi.addExpressions(clinit);
-      }
-      return multi;
-    }
-
-    /**
      * Creates a JMultiExpression from a set of JExpressionStatements,
      * optionally terminated by a JReturnStatement. If the method doesn't match
      * this pattern, it returns <code>null</code>.
@@ -243,9 +243,8 @@
      * expression of the method. If the method is void, the output of the
      * multi-expression should be considered undefined.
      */
-    private JMultiExpression createMultiExpressionFromBody(JMethodBody body,
-        boolean ignoringReturnValue) {
-      JMultiExpression multi = new JMultiExpression(body.getSourceInfo());
+    private List<JExpression> extractExpressionsFromBody(JMethodBody body) {
+      List<JExpression> expressions = Lists.newArrayList();
       CloneCalleeExpressionVisitor cloner = new CloneCalleeExpressionVisitor();
 
       for (JStatement stmt : body.getStatements()) {
@@ -253,16 +252,14 @@
           JExpressionStatement exprStmt = (JExpressionStatement) stmt;
           JExpression expr = exprStmt.getExpr();
           JExpression clone = cloner.cloneExpression(expr);
-          multi.addExpressions(clone);
+          expressions.add(clone);
         } else if (stmt instanceof JReturnStatement) {
           JReturnStatement returnStatement = (JReturnStatement) stmt;
           JExpression expr = returnStatement.getExpr();
           if (expr != null) {
-            if (!ignoringReturnValue || expr.hasSideEffects()) {
-              JExpression clone = cloner.cloneExpression(expr);
-              clone = maybeCast(clone, body.getMethod().getType());
-              multi.addExpressions(clone);
-            }
+            JExpression clone = cloner.cloneExpression(expr);
+            clone = maybeCast(clone, body.getMethod().getType());
+            expressions.add(clone);
           }
           // We hit an unconditional return; no need to evaluate anything else.
           break;
@@ -272,16 +269,18 @@
         }
       }
 
-      return multi;
+      return expressions;
     }
 
     /**
-     * Creates a multi expression for evaluating a method call instance,
+     * Creates a lists of expression for evaluating a method call instance,
      * possible clinit, and all arguments. This is a precursor for inlining the
      * remainder of a method that does not reference any parameters.
      */
-    private JMultiExpression createMultiExpressionIncludingArgs(JMethodCall x) {
-      JMultiExpression multi = createMultiExpressionForInstanceAndClinit(x);
+    private List<JExpression> expressionsIncludingArgs(JMethodCall x) {
+      List<JExpression> expressions = Lists.newArrayListWithCapacity(x.getArgs().size() + 2);
+      expressions.add(x.getInstance());
+      expressions.add(createClinitCall(x));
 
       for (int i = 0, c = x.getArgs().size(); i < c; ++i) {
         JExpression arg = x.getArgs().get(i);
@@ -289,50 +288,46 @@
         analyzer.accept(arg);
 
         if (analyzer.hasAssignment() || analyzer.canThrowException()) {
-          multi.addExpressions(arg);
+          expressions.add(arg);
         }
       }
-      return multi;
+      return expressions;
     }
 
     /**
-     * Replace the current expression with a given multi-expression and mark the
-     * method as modified. The dead-code elimination pass will optimize this if
-     * necessary.
+     * Replace the current expression with a given replacement and mark the
+     * method as modified.
      */
-    private void replaceWithMulti(Context ctx, JMultiExpression multi) {
-      ctx.replaceMe(multi);
+    private void replaceMeAndRecordModification(Context ctx, JExpression replacement) {
+      ctx.replaceMe(replacement);
       modifiedMethods.add(currentMethod);
     }
 
     /**
-     * Inline a call to an expression.
+     * Inline a call to an expression. Returns {@code InlineResult.BLACKLIST} if the method is
+     * deemed not inlineable regardless of call site; {@code InlineResult.DO_NOT_BLACKLIST}
+     * otherwise.
      */
-    private boolean tryInlineExpression(JMethodCall x, Context ctx, JMultiExpression targetExpr) {
-      /*
-       * Limit inlined methods to multiexpressions of length 2 for now. This
-       * handles the simple { return JVariableRef; } or { expression; return
-       * something; } cases.
-       *
-       * TODO: add an expression complexity analyzer.
-       */
-      if (targetExpr.getNumberOfExpressions() > 2) {
-        return false;
+    private InlineResult tryInlineBody(JMethodCall x, Context ctx,
+        List<JExpression> bodyAsExpressionList, boolean ignoringReturn) {
+
+      if (isTooComplexToInline(bodyAsExpressionList, ignoringReturn)) {
+        return InlineResult.BLACKLIST;
       }
 
       // Do not inline anything that modifies one of its params.
       ExpressionAnalyzer targetAnalyzer = new ExpressionAnalyzer();
-      targetAnalyzer.accept(targetExpr);
+      targetAnalyzer.accept(bodyAsExpressionList);
       if (targetAnalyzer.hasAssignmentToParameter()) {
-        return false;
+        return InlineResult.BLACKLIST;
       }
 
       // Make sure the expression we're about to inline doesn't include a call
       // to the target method!
       RecursionCheckVisitor recursionCheckVisitor = new RecursionCheckVisitor(x.getTarget());
-      recursionCheckVisitor.accept(targetExpr);
+      recursionCheckVisitor.accept(bodyAsExpressionList);
       if (recursionCheckVisitor.isRecursive()) {
-        return false;
+        return InlineResult.BLACKLIST;
       }
 
       /*
@@ -348,7 +343,8 @@
        * TODO: would this be possible in the trivial delegation case?
        */
       if (x.getTarget().getParams().size() != x.getArgs().size()) {
-        return true;
+        // Could not inline this call but the method might be inlineable at a different call site.
+        return InlineResult.DO_NOT_BLACKLIST;
       }
 
       // Run the order check. This verifies that all the parameters are
@@ -363,17 +359,18 @@
        * other expressions.
        */
       OrderVisitor orderVisitor = new OrderVisitor(x.getTarget().getParams());
-      orderVisitor.accept(targetExpr);
+      orderVisitor.accept(bodyAsExpressionList);
 
       /*
        * A method that doesn't touch any parameters is trivially inlinable (this
        * covers the empty method case)
        */
       if (orderVisitor.checkResults() == SideEffectCheck.NO_REFERENCES) {
-        JMultiExpression multi = createMultiExpressionIncludingArgs(x);
-        multi.addExpressions(targetExpr);
-        replaceWithMulti(ctx, multi);
-        return true;
+        List<JExpression> expressions = expressionsIncludingArgs(x);
+        expressions.addAll(bodyAsExpressionList);
+        replaceMeAndRecordModification(ctx,
+            JjsUtils.createOptimizedMultiExpression(ignoringReturn, expressions));
+        return InlineResult.DO_NOT_BLACKLIST;
       }
 
       /*
@@ -393,22 +390,44 @@
              * This argument evaluation could affect or be affected by the
              * callee so we cannot inline here.
              */
-            return true;
+            // Could not inline this call but the method is potentially inlineable.
+            return InlineResult.DO_NOT_BLACKLIST;
           }
         }
       }
 
       // We're safe to inline.
-      JMultiExpression multi = createMultiExpressionForInstanceAndClinit(x);
 
       // Replace all params in the target expression with the actual arguments.
       ParameterReplacer replacer = new ParameterReplacer(x);
-      replacer.accept(targetExpr);
+      replacer.accept(bodyAsExpressionList);
+      bodyAsExpressionList.add(0, x.getInstance());
+      bodyAsExpressionList.add(1, createClinitCall(x));
+      replaceMeAndRecordModification(ctx, JjsUtils.createOptimizedMultiExpression(ignoringReturn,
+          bodyAsExpressionList));
+      return InlineResult.DO_NOT_BLACKLIST;
+    }
+  }
 
-      multi.addExpressions(targetExpr);
-      replaceWithMulti(ctx, multi);
+  private static boolean isTooComplexToInline(List<JExpression> bodyAsExpressionList,
+      boolean ignoringReturn) {
+    /*
+     * Limit inlined methods to multiexpressions of length 2 for now. This
+     * handles the simple { return JVariableRef; } or { expression; return
+     * something; } cases.
+     *
+     * TODO: add an expression complexity analyzer.
+     */
+    if (ignoringReturn) {
+      return bodyAsExpressionList.size() > 2;
+    }
+
+    if (bodyAsExpressionList.size() == 3 && bodyAsExpressionList.get(2).hasSideEffects()) {
       return true;
     }
+
+    // The expression is effectively of size 2, hence not too complex to inline.
+    return false;
   }
 
   /**
@@ -575,11 +594,14 @@
 
   private JReferenceType merge(JReferenceType source, JReferenceType target) {
     JReferenceType result;
-    if (program.typeOracle.canTriviallyCast(source.getUnderlyingType(), target.getUnderlyingType())) {
+    if (program.typeOracle.canTriviallyCast(
+        source.getUnderlyingType(), target.getUnderlyingType())) {
       result = source;
     } else {
       result = target;
     }
     return result;
   }
+
+  private enum InlineResult { BLACKLIST, DO_NOT_BLACKLIST}
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
index 7ff1ad6..7ba0e69 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
@@ -37,7 +37,6 @@
 import com.google.gwt.dev.jjs.ast.JMethodCall;
 import com.google.gwt.dev.jjs.ast.JModVisitor;
 import com.google.gwt.dev.jjs.ast.JNewInstance;
-import com.google.gwt.dev.jjs.ast.JNullLiteral;
 import com.google.gwt.dev.jjs.ast.JNullType;
 import com.google.gwt.dev.jjs.ast.JParameter;
 import com.google.gwt.dev.jjs.ast.JParameterRef;
@@ -53,12 +52,13 @@
 import com.google.gwt.dev.jjs.ast.JVariable;
 import com.google.gwt.dev.jjs.ast.JVariableRef;
 import com.google.gwt.dev.jjs.ast.JVisitor;
-import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
 import com.google.gwt.dev.jjs.ast.js.JsniFieldRef;
 import com.google.gwt.dev.jjs.ast.js.JsniMethodRef;
 import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
+import com.google.gwt.thirdparty.guava.common.base.Predicate;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -196,8 +196,9 @@
    * nodes onto sets of JReferenceType directly, which meant I had to rerun this
    * visitor each time.
    */
-  public class RecordVisitor extends JVisitor {
+  private class RecordVisitor extends JVisitor {
     private JMethod currentMethod;
+    private Predicate<JField> canUninitializedValueBeObserved;
 
     @Override
     public void endVisit(JBinaryOperation x, Context ctx) {
@@ -231,9 +232,11 @@
 
     @Override
     public void endVisit(JField x, Context ctx) {
-      if (x.getLiteralInitializer() != null) {
-        // TODO: do I still need this?
-        addAssignment(x, x.getLiteralInitializer());
+      if (x.hasInitializer() && canUninitializedValueBeObserved.apply(x)) {
+        addAssignment(x, x.getType().getDefaultValue());
+      }
+      if (!x.hasInitializer()) {
+        addAssignment(x, x.getType().getDefaultValue());
       }
       currentMethod = null;
     }
@@ -330,6 +333,12 @@
       return true;
     }
 
+    public void record(JProgram program) {
+      canUninitializedValueBeObserved = ComputePotentiallyObservableUninitializedValues
+          .analyze(program);
+      accept(program);
+    }
+
     private void addAssignment(JVariable target, JExpression rhs) {
       add(target, rhs, assignments);
     }
@@ -476,20 +485,22 @@
 
       switch (analysisResult) {
         case TRUE:
-        // replace with a simple null test
-          JNullLiteral nullLit = program.getLiteralNull();
-          JBinaryOperation neq =
-              new JBinaryOperation(x.getSourceInfo(), program.getTypePrimitiveBoolean(),
-                  JBinaryOperator.NEQ, x.getExpr(), nullLit);
-          ctx.replaceMe(neq);
+          if (x.getExpr().getType().canBeNull()) {
+          // replace with a simple null test
+            JBinaryOperation neq =
+                new JBinaryOperation(x.getSourceInfo(), program.getTypePrimitiveBoolean(),
+                    JBinaryOperator.NEQ, x.getExpr(), program.getLiteralNull());
+            ctx.replaceMe(neq);
+          } else {
+            ctx.replaceMe(
+                JjsUtils.createOptimizedMultiExpression(x.getExpr(),
+                    program.getLiteralBoolean(true)));
+          }
           break;
         case FALSE:
         // replace with a false literal
-          JExpression result = program.getLiteralBoolean(false);
-          if (x.getExpr().hasSideEffects()) {
-            result = new JMultiExpression(x.getSourceInfo(), x.getExpr(), result);
-          }
-          ctx.replaceMe(result);
+          ctx.replaceMe(
+              JjsUtils.createOptimizedMultiExpression(x.getExpr(), program.getLiteralBoolean(false)));
           break;
         case UNKNOWN:
         default:
@@ -718,19 +729,7 @@
       }
 
       // tighten based on assignment
-      List<JReferenceType> typeList = new ArrayList<JReferenceType>();
-
-      /*
-       * For fields that are not compile time constants, add a null assignment, because the
-       * field might be accessed before initialized. Technically even a field
-       * with an initializer might be accessed before initialization, but
-       * presumably that is not the programmer's intent, so the compiler cheats
-       * and assumes the initial null will not be seen.
-       */
-      if (!cannotBeSeenUninitialized(x)) {
-        typeList.add(typeNull);
-      }
-
+      List<JReferenceType> typeList = Lists.newArrayList();
       Collection<JExpression> myAssignments = assignments.get(x);
       if (myAssignments != null) {
         for (JExpression expr : myAssignments) {
@@ -868,7 +867,7 @@
   private OptimizerStats execImpl() {
     OptimizerStats stats = new OptimizerStats(NAME);
     RecordVisitor recorder = new RecordVisitor();
-    recorder.accept(program);
+    recorder.record(program);
 
     /*
      * We must iterate multiple times because each way point we tighten creates
diff --git a/dev/core/src/com/google/gwt/dev/util/collect/Stack.java b/dev/core/src/com/google/gwt/dev/util/collect/Stack.java
index e76e0bf..53077af 100644
--- a/dev/core/src/com/google/gwt/dev/util/collect/Stack.java
+++ b/dev/core/src/com/google/gwt/dev/util/collect/Stack.java
@@ -87,4 +87,11 @@
   public void push(T value) {
     elements.add(value);
   }
+
+  /**
+   * Creates a new Stack for type {@code T}.
+   */
+  public static <T> Stack<T> create() {
+    return new Stack<T>();
+  }
 }
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/ComputePotentiallyObservableUninitializedValuesTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/ComputePotentiallyObservableUninitializedValuesTest.java
new file mode 100644
index 0000000..ae6bf37
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/ComputePotentiallyObservableUninitializedValuesTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2014 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.jjs.impl;
+
+import com.google.gwt.dev.jjs.ast.JField;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.thirdparty.guava.common.base.Predicate;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
+
+/**
+ * Tests {@link ComputePotentiallyObservableUninitializedValues}.
+ */
+public class ComputePotentiallyObservableUninitializedValuesTest extends OptimizerTestBase {
+
+  private boolean runMethodInliner;
+  private boolean runSpecializer;
+
+  @Override
+  public void setUp() throws Exception {
+    runMethodInliner = false;
+    runSpecializer = false;
+  }
+
+  public void testSimpleClass() throws Exception {
+    addSnippetClassDecl(
+        "static class A  { ",
+        "  int i1 = 1;",
+        "  Integer i2 = new Integer(1);",
+        "  final int fi1 = 1;",
+        "  final Integer fi2 = new Integer(1);",
+        "}");
+    JProgram program = compileSnippet("void", "return;");
+    assertAnalysisCorrect(program, ImmutableList.<String>of(),
+        ImmutableList
+            .of("EntryPoint$A.i1", "EntryPoint$A.i2", "EntryPoint$A.fi1", "EntryPoint$A.fi2"));
+  }
+
+  public void testOnlyFinalLiteralUnobservable() throws Exception {
+    addSnippetClassDecl(
+        "static class A  { ",
+        "  A() { m(); }",
+        "  void m() { }",
+        "}");
+    addSnippetClassDecl(
+        "static class B extends A { ",
+        "  int i1 = 1;",
+        "  Integer i2 = new Integer(1);",
+        "  final int fi1 = 1;",
+        "  final Integer fi2 = new Integer(1);",
+        "}");
+    JProgram program = compileSnippet("void", "return;");
+    assertAnalysisCorrect(program,
+        ImmutableList.of("EntryPoint$B.i1", "EntryPoint$B.i2", "EntryPoint$B.fi2"),
+        ImmutableList.of("EntryPoint$B.fi1"));
+  }
+
+  public void testSafeCustomInitializer() throws Exception {
+    addSnippetClassDecl(
+        "static class A  { ",
+        "  { m(); }",
+        "  static void m() { }",
+        "}");
+    addSnippetClassDecl(
+        "static class B extends A { ",
+        "  int i1 = 1;",
+        "  Integer i2 = new Integer(1);",
+        "  final int fi1 = 1;",
+        "  final Integer fi2 = new Integer(1);",
+        "}");
+    JProgram program = compileSnippet("void", "return;");
+    assertAnalysisCorrect(program, ImmutableList.<String>of(),
+        ImmutableList
+            .of("EntryPoint$B.fi1", "EntryPoint$B.i1", "EntryPoint$B.i2", "EntryPoint$B.fi2"));
+    MakeCallsStatic.exec(program, false);
+    assertAnalysisCorrect(program, ImmutableList.<String>of(),
+        ImmutableList
+            .of("EntryPoint$B.fi1", "EntryPoint$B.i1", "EntryPoint$B.i2", "EntryPoint$B.fi2"));
+  }
+
+  public void testSafeStaticCall() throws Exception {
+    addSnippetClassDecl(
+        "static class A  { ",
+        "  A() { m(new A(), 2); }",
+        "  static void m(A a, int i) { }",
+        "}");
+    addSnippetClassDecl(
+        "static class B extends A { ",
+        "  int i1 = 1;",
+        "  Integer i2 = new Integer(1);",
+        "  final int fi1 = 1;",
+        "  final Integer fi2 = new Integer(1);",
+        "}");
+    JProgram program = compileSnippet("void", "return;");
+    assertAnalysisCorrect(program, ImmutableList.<String>of(),
+        ImmutableList
+            .of("EntryPoint$B.fi1", "EntryPoint$B.i1", "EntryPoint$B.i2", "EntryPoint$B.fi2"));
+  }
+
+  public void testSafePolyCall() throws Exception {
+    addSnippetClassDecl(
+        "static class A  { ",
+        "  A() { String s = new Integer(0).toString(); }",
+        "}");
+    addSnippetClassDecl(
+        "static class B extends A { ",
+        "  int i1 = 1;",
+        "  Integer i2 = new Integer(1);",
+        "  final int fi1 = 1;",
+        "  final Integer fi2 = new Integer(1);",
+        "}");
+    JProgram program = compileSnippet("void", "return;");
+    assertAnalysisCorrect(program, ImmutableList.<String>of(),
+        ImmutableList
+            .of("EntryPoint$B.fi1", "EntryPoint$B.i1", "EntryPoint$B.i2", "EntryPoint$B.fi2"));
+  }
+
+  public void testUnSafeCustomInitializer_polymorphicDispatch() throws Exception {
+    addSnippetClassDecl(
+        "static class A  { ",
+        "  void m() { }",
+        "  { m(); }",
+        "}");
+    addSnippetClassDecl(
+        "static class B extends A { ",
+        "  int i1 = 1;",
+        "  Integer i2 = new Integer(1);",
+        "  final int fi1 = 1;",
+        "  final Integer fi2 = new Integer(1);",
+        "}");
+    JProgram program = compileSnippet("void", "return;");
+    assertAnalysisCorrect(program,
+        ImmutableList.of("EntryPoint$B.i1", "EntryPoint$B.i2", "EntryPoint$B.fi2"),
+        ImmutableList.of("EntryPoint$B.fi1"));
+  }
+
+  public void testUnSafeCustomInitializer_escapingThis() throws Exception {
+    addSnippetClassDecl(
+        "static class A  { ",
+        "  static void m(A a) { }",
+        "  { m(this); }",
+        "}");
+    addSnippetClassDecl(
+        "static class B extends A { ",
+        "  int i1 = 1;",
+        "  Integer i2 = new Integer(1);",
+        "  final int fi1 = 1;",
+        "  final Integer fi2 = new Integer(1);",
+        "}");
+    JProgram program = compileSnippet("void", "return;");
+    assertAnalysisCorrect(program,
+        ImmutableList.of("EntryPoint$B.i1", "EntryPoint$B.i2", "EntryPoint$B.fi2"),
+        ImmutableList.of("EntryPoint$B.fi1"));
+  }
+
+  public void testUnSafeCustomInitializer_escapingThisThroughArrayInitializer() throws Exception {
+    addSnippetClassDecl(
+        "static class A  { ",
+        "  { A[] a = new A[] {this}; }",
+        "}");
+    addSnippetClassDecl(
+        "static class B extends A { ",
+        "  int i1 = 1;",
+        "  Integer i2 = new Integer(1);",
+        "  final int fi1 = 1;",
+        "  final Integer fi2 = new Integer(1);",
+        "}");
+    JProgram program = compileSnippet("void", "return;");
+    assertAnalysisCorrect(program,
+        ImmutableList.of("EntryPoint$B.i1", "EntryPoint$B.i2", "EntryPoint$B.fi2"),
+        ImmutableList.of("EntryPoint$B.fi1"));
+  }
+
+  public void testUnSafeCustomInitializer_escapingDeepReference() throws Exception {
+    addSnippetClassDecl(
+        "static class A  { ",
+        "  A() {String s = (new Integer(0).toString() + this.toString()).substring(0, 1); }",
+        "}");
+    addSnippetClassDecl(
+        "static class B extends A { ",
+        "  int i1 = 1;",
+        "  Integer i2 = new Integer(1);",
+        "  final int fi1 = 1;",
+        "  final Integer fi2 = new Integer(1);",
+        "}");
+    JProgram program = compileSnippet("void", "return;");
+    assertAnalysisCorrect(program,
+        ImmutableList.of("EntryPoint$B.i1", "EntryPoint$B.i2", "EntryPoint$B.fi2"),
+        ImmutableList.of("EntryPoint$B.fi1"));
+    MakeCallsStatic.exec(program, false); // required so that method is static
+    assertAnalysisCorrect(program,
+        ImmutableList.of("EntryPoint$B.i1", "EntryPoint$B.i2", "EntryPoint$B.fi2"),
+        ImmutableList.of("EntryPoint$B.fi1"));
+  }
+
+  @Override
+  protected boolean optimizeMethod(JProgram program, JMethod method) {
+
+    if (runMethodInliner) {
+      MethodInliner.exec(program);
+    }
+    if (runSpecializer) {
+      Finalizer.exec(program); // required so that method is marked final
+      MakeCallsStatic.exec(program, false); // required so that method is static
+      TypeTightener.exec(program); // required so that the parameter types are tightened
+      MethodCallSpecializer.exec(program);
+    }
+
+    OptimizerStats result = DeadCodeElimination.exec(program, method);
+    if (result.didChange()) {
+      // Make sure we converge in one pass.
+      //
+      // TODO(rluble): It does not appear to be true in general unless we iterate until a
+      // fixpoint in exec().
+      //
+      // Example:
+      //
+      //     Constructor( ) { deadcode }
+      //     m( new Constructor(); }
+      //
+      // If m is processed first, it will see the constructor as having side effects.
+      // Then the constructor will become empty enabling m() become empty in the next round.
+      //
+      assertFalse(DeadCodeElimination.exec(program, method).didChange());
+    }
+    return result.didChange();
+  }
+
+  private void assertAnalysisCorrect(JProgram program,
+      Iterable<String> fieldsThatCanBeObservedUninitialized,
+      Iterable<String> fieldsThatCannotBeObservedUninitialized) {
+    Predicate<JField> uninitializedValuesCanBeObserved =
+        ComputePotentiallyObservableUninitializedValues.analyze(program);
+
+    for (String fieldName : fieldsThatCanBeObservedUninitialized) {
+      JField field = findField(program, fieldName);
+      assertNotNull(field);
+      assertTrue("Field " + fieldName + " was erroneously determined to uninitialized unobservable",
+          uninitializedValuesCanBeObserved.apply(field));
+    }
+    for (String fieldName : fieldsThatCannotBeObservedUninitialized) {
+      JField field = findField(program, fieldName);
+      assertNotNull(field);
+      assertFalse("Field " + fieldName + " was erroneously determined to uninitialized observable",
+          uninitializedValuesCanBeObserved.apply(field));
+    }
+  }
+}
diff --git a/user/test/com/google/gwt/dev/jjs/test/FieldInitOrderChild.java b/user/test/com/google/gwt/dev/jjs/test/FieldInitOrderChild.java
index 56df745..8281cdf 100644
--- a/user/test/com/google/gwt/dev/jjs/test/FieldInitOrderChild.java
+++ b/user/test/com/google/gwt/dev/jjs/test/FieldInitOrderChild.java
@@ -56,19 +56,8 @@
     // i3, i4 and i7 would be directly converted into strings hence show undefined instead of null.
     // String operations should take care of corner cases where a string is null or undefined
     // see issue 8257.
-    seenValues.add("i1=" + i1 + ",i2=" + i2 + ",i3=" + undefinedToNull(i3) +
-        ",i4=" +  undefinedToNull(i4) + ",i5=" + i5 + ",i6=" + i6
-        + ",i7=" + undefinedToNull(i7));
-  }
-
-  // Convert undefined to null this way {@code undefinedToNull(i)} instead of
-  // {@code i == null ? "null" : i} to avoid the shortcomings of our nullness analysis in
-  // {@link TypeTightener}, which will infer that i3 is never null.
-  private String undefinedToNull(Object o) {
-    String s = "" + o;
-    if (s.equals("undefined")) {
-      return "null";
-    }
-    return s;
+    seenValues.add("i1=" + i1 + ",i2=" + i2 + ",i3=" + (i3 == null ? null : i3) +
+        ",i4=" +  (i4 == null ? null : i4) + ",i5=" + i5 + ",i6=" + i6
+        + ",i7=" + (i7 == null ? null : i7));
   }
 }
\ No newline at end of file