ControlFlowAnalyzer JsInterop overhaul.

 - Major simplifications the special casing of JsInterop types
   in ControlFlowAnalyzer.
 - Readability improvements.
 - Removes the extra pass that collects the casting data.
 - Adds more jsinterop tests.

Change-Id: I5f7ad99dd5e879683b2c5bb262f3b2e5cc4eab20
Review-Link: https://gwt-review.googlesource.com/#/c/12740/
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index 03a1c26..5bef255 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -68,7 +68,6 @@
 import com.google.gwt.dev.jjs.impl.CompileTimeConstantsReplacer;
 import com.google.gwt.dev.jjs.impl.ComputeCastabilityInformation;
 import com.google.gwt.dev.jjs.impl.ComputeExhaustiveCastabilityInformation;
-import com.google.gwt.dev.jjs.impl.ComputeInstantiatedJsoInterfaces;
 import com.google.gwt.dev.jjs.impl.ControlFlowAnalyzer;
 import com.google.gwt.dev.jjs.impl.ControlFlowRecorder;
 import com.google.gwt.dev.jjs.impl.DeadCodeElimination;
@@ -478,7 +477,6 @@
             !shouldOptimize() /* recordTrivialCasts */);
       }
 
-      ComputeInstantiatedJsoInterfaces.exec(jprogram);
       ImplementCastsAndTypeChecks.exec(jprogram, options.isCastCheckingDisabled(),
           shouldOptimize() /* pruneTrivialCasts */);
       ArrayNormalizer.exec(jprogram, options.isCastCheckingDisabled());
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
index 54974e4..3ff2567 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
@@ -132,16 +132,6 @@
     private String javaLangObject;
   }
 
-  private Set<JReferenceType> instantiatedJsoTypesViaCast = Sets.newHashSet();
-
-  public void setInstantiatedJsoTypesViaCast(Set<JReferenceType> instantiatedJsoTypesViaCast) {
-    this.instantiatedJsoTypesViaCast = instantiatedJsoTypesViaCast;
-  }
-
-  public Set<JReferenceType> getInstantiatedJsoTypesViaCast() {
-    return instantiatedJsoTypesViaCast;
-  }
-
   /**
    * A method needs a JsInterop bridge if any of the following are true:
    * 1) the method name conflicts with a method name of a non-JsType/JsExport method in a superclass
@@ -548,13 +538,6 @@
     return false;
   }
 
-  /**
-   * True if the type is a JSO or interface implemented by a JSO, or a JsType, or a JsFunction.
-   */
-  public boolean canBeInstantiatedInJavascript(JType type) {
-    return canBeJavaScriptObject(type) || isJsType(type) || isJsFunction(type);
-  }
-
   public boolean castFailsTrivially(JReferenceType fromType, JReferenceType toType) {
     if (!fromType.canBeNull() && toType.isNullType()) {
       // Cannot cast non-nullable to null
@@ -999,14 +982,8 @@
         return true;
       }
     }
-    // TODO(rluble): ControlFlowAnalyzer should be responsible for making sure that these types
-    // are considered live. THIS IS A HACK. In particular this method and the specialized
-    // version for JDeclaredType should have the same semantics.
-    return isJsType(type) || hasAnyExports(type) || isJsFunction(type);
-  }
 
-  private boolean hasAnyExports(JReferenceType type) {
-    return type instanceof JDeclaredType ? ((JDeclaredType) type).hasAnyExports() : false;
+    return false;
   }
 
   private boolean isArrayInterface(JType type) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ComputeInstantiatedJsoInterfaces.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ComputeInstantiatedJsoInterfaces.java
deleted file mode 100644
index 268f266..0000000
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ComputeInstantiatedJsoInterfaces.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2008 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.JCastOperation;
-import com.google.gwt.dev.jjs.ast.JClassType;
-import com.google.gwt.dev.jjs.ast.JDeclaredType;
-import com.google.gwt.dev.jjs.ast.JInterfaceType;
-import com.google.gwt.dev.jjs.ast.JProgram;
-import com.google.gwt.dev.jjs.ast.JReferenceType;
-import com.google.gwt.dev.jjs.ast.JType;
-import com.google.gwt.dev.jjs.ast.JVisitor;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Record all Cast operations on JSOs as instantiations. This must run before
- * {@link com.google.gwt.dev.jjs.impl.ImplementCastsAndTypeChecks}.
- */
-public class ComputeInstantiatedJsoInterfaces {
-  class InstantiatedJsoInterfacesCollector extends JVisitor {
-    @Override
-    public void endVisit(JCastOperation x, Context ctx) {
-      JType toType = x.getCastType();
-
-      if (toType instanceof JReferenceType && !toType.isNullType()) {
-        toType = toType.getUnderlyingType();
-        if (program.typeOracle.willCrossCastLikeJso(toType) ||
-            program.typeOracle.isOrExtendsJsType(toType, true) ||
-            program.typeOracle.isJsFunction(toType)) {
-          instantiateJsoInterface((JReferenceType) toType);
-        }
-      }
-    }
-  }
-
-  private void instantiateJsoInterface(JReferenceType toType) {
-    if (instantiatedJsoTypes.add(toType)) {
-      if (program.typeOracle.getSingleJsoImpl(toType) != null) {
-        // rescuing an Interface via Cast, we record the JSO implementing it
-        instantiateJsoInterface(program.typeOracle.getSingleJsoImpl(toType));
-      }
-      // if it's a class, and the superType is JSO, rescue it too
-      if (toType instanceof JClassType) {
-        JClassType superType = ((JClassType) toType).getSuperClass();
-        if (superType != null && superType.isJsoType()) {
-          instantiateJsoInterface(superType);
-        }
-      }
-      // if we extend another JsType, or Interface with JSO implementation, rescue it
-      for (JInterfaceType intf : ((JDeclaredType) toType).getImplements()) {
-        if (intf.isJsType() || program.typeOracle.getSingleJsoImpl(intf) != null) {
-          instantiateJsoInterface(intf);
-        }
-      }
-    }
-  }
-
-  private final JProgram program;
-  private final Set<JReferenceType> instantiatedJsoTypes = new HashSet<JReferenceType>();
-
-  public static void exec(JProgram program) {
-    new ComputeInstantiatedJsoInterfaces(program).execImpl();
-  }
-
-  private ComputeInstantiatedJsoInterfaces(JProgram program) {
-    this.program = program;
-  }
-
-  private void execImpl() {
-    InstantiatedJsoInterfacesCollector replacer = new InstantiatedJsoInterfacesCollector();
-    replacer.accept(program);
-    program.typeOracle.setInstantiatedJsoTypesViaCast(instantiatedJsoTypes);
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
index 4582834..05cc741 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
@@ -17,6 +17,8 @@
 
 import com.google.gwt.dev.jjs.ast.Context;
 import com.google.gwt.dev.jjs.ast.JAbsentArrayDimension;
+import com.google.gwt.dev.jjs.ast.JArrayLength;
+import com.google.gwt.dev.jjs.ast.JArrayRef;
 import com.google.gwt.dev.jjs.ast.JArrayType;
 import com.google.gwt.dev.jjs.ast.JBinaryOperation;
 import com.google.gwt.dev.jjs.ast.JBinaryOperator;
@@ -29,6 +31,7 @@
 import com.google.gwt.dev.jjs.ast.JExpression;
 import com.google.gwt.dev.jjs.ast.JField;
 import com.google.gwt.dev.jjs.ast.JFieldRef;
+import com.google.gwt.dev.jjs.ast.JInstanceOf;
 import com.google.gwt.dev.jjs.ast.JInterfaceType;
 import com.google.gwt.dev.jjs.ast.JLocal;
 import com.google.gwt.dev.jjs.ast.JLocalRef;
@@ -92,6 +95,18 @@
     private final List<JMethod> curMethodStack = Lists.newArrayList();
 
     @Override
+    public boolean visit(JArrayRef arrayRef, Context ctx) {
+      maybeRescueJsTypeArray(arrayRef.getInstance().getType());
+      return true;
+    }
+
+    @Override
+    public boolean visit(JArrayLength arrayLength, Context ctx) {
+      maybeRescueJsTypeArray(arrayLength.getInstance().getType());
+      return true;
+    }
+
+    @Override
     public boolean visit(JArrayType type, Context ctx) {
       assert (referencedTypes.contains(type));
       boolean isInstantiated = instantiatedTypes.contains(type);
@@ -105,23 +120,23 @@
         JClassType superClass = ((JClassType) leafType).getSuperClass();
         if (superClass != null) {
           // FooSub[] -> Foo[]
-          rescue(program.getOrCreateArrayType(superClass, dims), true, isInstantiated);
+          rescue(program.getOrCreateArrayType(superClass, dims), isInstantiated);
           didSuperType = true;
         }
       } else if (leafType instanceof JInterfaceType) {
         // Intf[] -> Object[]
         rescue(program.getOrCreateArrayType(program.getTypeJavaLangObject(), dims),
-            true, isInstantiated);
+            isInstantiated);
         didSuperType = true;
       }
       if (!didSuperType) {
         if (dims > 1) {
           // anything[][] -> Object[]
-          rescue(program.getOrCreateArrayType(program.getTypeJavaLangObject(), dims - 1), true,
+          rescue(program.getOrCreateArrayType(program.getTypeJavaLangObject(), dims - 1),
               isInstantiated);
         } else {
           // anything[] -> Object
-          rescue(program.getTypeJavaLangObject(), true, isInstantiated);
+          rescue(program.getTypeJavaLangObject(), isInstantiated);
         }
       }
 
@@ -130,7 +145,7 @@
         JDeclaredType dLeafType = (JDeclaredType) leafType;
         for (JInterfaceType intfType : dLeafType.getImplements()) {
           JArrayType intfArray = program.getOrCreateArrayType(intfType, dims);
-          rescue(intfArray, true, isInstantiated);
+          rescue(intfArray, isInstantiated);
         }
       }
 
@@ -186,10 +201,10 @@
     public boolean visit(JCastOperation x, Context ctx) {
       // Rescue any JavaScriptObject type that is the target of a cast.
       JType targetType = x.getCastType();
-      if (!program.typeOracle.canBeInstantiatedInJavascript(targetType)) {
+      if (!canBeInstantiatedInJavaScript(targetType)) {
         return true;
       }
-      rescue((JReferenceType) targetType, true, true);
+      rescue((JReferenceType) targetType, true);
       JType exprType = x.getExpr().getType();
       if (program.typeOracle.isSingleJsoImpl(targetType)) {
         /*
@@ -203,14 +218,8 @@
           // source is JSO or SingleJso interface whose implementor is live
           JClassType jsoImplementor =
               program.typeOracle.getSingleJsoImpl((JReferenceType) targetType);
-          if (jsoImplementor != null) {
-            rescue(jsoImplementor, true, true);
-          }
+          rescue(jsoImplementor, true);
         }
-      } else if (program.typeOracle.isJsType(targetType)
-        && ((JDeclaredType) targetType).getJsPrototype() != null) {
-        // keep alive JsType with prototype used in cast so it can used in cast checks against JS objects later
-        rescue((JReferenceType) targetType, true, true);
       }
 
       return true;
@@ -230,20 +239,18 @@
       boolean isInstantiated = instantiatedTypes.contains(type);
 
       // Rescue my super type
-      rescue(type.getSuperClass(), true, isInstantiated);
+      rescue(type.getSuperClass(), isInstantiated);
 
       // Rescue my clinit (it won't ever be explicitly referenced)
       if (type.hasClinit()) {
         rescue(type.getClinitMethod());
       }
 
-      // JLS 12.4.1: don't rescue my super interfaces just because I'm rescued.
-      // However, if I'm instantiated, let's mark them as instantiated.
-      for (JInterfaceType intfType : type.getImplements()) {
-        rescue(intfType, false, isInstantiated);
+      // If I'm instantiated, let's mark super interfaces as instantiated.
+      if (isInstantiated) {
+        rescueMembersAndInstantiateSuperInterfaces(type);
       }
 
-      rescueMembersIfInstantiable(type);
       return false;
     }
 
@@ -289,7 +296,7 @@
        * the enclosing types for any static fields that make it here.
        */
       if (target.isStatic()) {
-        rescue(target.getEnclosingType(), true, false);
+        rescue(target.getEnclosingType(), false);
       }
       if (target.isStatic() || instantiatedTypes.contains(target.getEnclosingType())) {
         rescue(target);
@@ -303,6 +310,16 @@
     }
 
     @Override
+    public boolean visit(JInstanceOf x, Context ctx) {
+      JReferenceType targetType = x.getTestType();
+      if (program.typeOracle.isJsType(targetType) || program.typeOracle.isJsFunction(targetType)) {
+        // keep alive JsType used in instanceof so it is not pruned and re-written as null check.
+        rescue(targetType, true);
+      }
+      return true;
+    }
+
+    @Override
     public boolean visit(JInterfaceType type, Context ctx) {
       boolean isReferenced = referencedTypes.contains(type);
       boolean isInstantiated = instantiatedTypes.contains(type);
@@ -313,15 +330,11 @@
         rescue(type.getClinitMethod());
       }
 
-      // JLS 12.4.1: don't rescue my super interfaces just because I'm rescued.
-      // However, if I'm instantiated, let's mark them as instantiated.
+      // If I'm instantiated, let's mark super interfaces as instantiated.
       if (isInstantiated) {
-        for (JInterfaceType intfType : type.getImplements()) {
-          rescue(intfType, false, true);
-        }
+        rescueMembersAndInstantiateSuperInterfaces(type);
       }
 
-      rescueMembersIfInstantiable(type);
       return false;
     }
 
@@ -338,10 +351,10 @@
       if (enclosingType.isJsoType()) {
         // Calls to JavaScriptObject types rescue those types.
         boolean instance = !x.isStatic() || program.isStaticImpl(x);
-        rescue(enclosingType, true, instance);
+        rescue(enclosingType, instance);
       } else if (x.isStatic()) {
         // JLS 12.4.1: references to static methods rescue the enclosing class
-        rescue(enclosingType, true, false);
+        rescue(enclosingType, false);
       }
 
       if (x.isNative()) {
@@ -405,7 +418,14 @@
         return true;
       }
 
-      return rescueArgumentsIfParametersCanBeRead(call, method);
+      if (call.getInstance() != null) {
+        // Explicitly visit instance since we're returning false below.
+        this.accept(call.getInstance());
+      }
+
+      rescueArgumentsIfParametersCanBeRead(call);
+
+      return false;
     }
 
     @Override
@@ -421,11 +441,11 @@
           if (newArray.dims.get(i) instanceof JAbsentArrayDimension) {
             break;
           }
-          rescue(program.getOrCreateArrayType(leafType, nDims - i), true, true);
+          rescue(program.getOrCreateArrayType(leafType, nDims - i), true);
         }
       } else {
         // just rescue my own specific type
-        rescue(arrayType, true, true);
+        rescue(arrayType, true);
       }
       return true;
     }
@@ -433,7 +453,7 @@
     @Override
     public boolean visit(JNewInstance x, Context ctx) {
       // rescue and instantiate the target class!
-      rescueAndInstantiate(x.getClassType());
+      rescue(x.getClassType(), true);
       return super.visit(x, ctx);
     }
 
@@ -488,7 +508,7 @@
       if (x.getTarget() instanceof JConstructor) {
         // But if a constructor is targeted, there is an implicit 'new' op.
         JConstructor ctor = (JConstructor) x.getTarget();
-        rescueAndInstantiate(ctor.getEnclosingType());
+        rescue(ctor.getEnclosingType(), true);
       }
       return visit((JMethodCall) x, ctx);
     }
@@ -498,13 +518,14 @@
       liveStrings.add(literal.getValue());
 
       // rescue and instantiate java.lang.String
-      rescue(program.getTypeJavaLangString(), true, true);
+      rescue(program.getTypeJavaLangString(), true);
       return true;
     }
 
     private boolean canBeInstantiatedInJavaScript(JType type) {
-      if (program.typeOracle.canBeInstantiatedInJavascript(type) ||
-          program.isJavaLangString(type)) {
+      // Technically, JsType/JsFunction are also instantiatable in JavaScript but we don't track
+      // them using similar to JSO as if we do that then after cast normalization, they got pruned.
+      if (program.typeOracle.canBeJavaScriptObject(type) || program.isJavaLangString(type)) {
         return true;
       }
 
@@ -581,13 +602,11 @@
       if (!canBeInstantiatedInJavaScript(type)) {
         return;
       }
-      rescue((JReferenceType) type, true, true);
+      rescue((JReferenceType) type, true);
       if (program.typeOracle.isSingleJsoImpl(type)) {
         // Cast of JSO into SingleJso interface, rescue the implementor if exists
         JClassType singleJsoImpl = program.typeOracle.getSingleJsoImpl((JReferenceType) type);
-        if (singleJsoImpl != null) {
-          rescue(singleJsoImpl, true, true);
-        }
+        rescue(singleJsoImpl, true);
       }
     }
 
@@ -596,8 +615,19 @@
         return;
       }
 
-      if (!liveFieldsAndMethods.contains(method)) {
-        liveFieldsAndMethods.add(method);
+      if (liveFieldsAndMethods.add(method)) {
+        JDeclaredType enclosingType = method.getEnclosingType();
+        // If any method reachable for JsType/Function interface, we rescue it so it is not pruned.
+        // This is simpler than tracking where the objects may enter the system and cheap as
+        // interfaces doesn't cost much in the output.
+        // More appropriate solution is to track casts and JSNI methods (see
+        // #canBeInstantiatedInJavaScript) but unfortunately casts are replaced at a later stage
+        // that causes type and all calls to be pruned.
+        if (enclosingType instanceof JInterfaceType
+            && (method.isJsTypeMember() || method.isJsFunctionMethod())) {
+          rescue(enclosingType, true);
+        }
+
         membersToRescueIfTypeIsInstantiated.remove(method);
         if (dependencyRecorder != null) {
           curMethodStack.add(method);
@@ -607,43 +637,22 @@
         if (dependencyRecorder != null) {
           curMethodStack.remove(curMethodStack.size() - 1);
         }
-        if (method.isNative() || method.isOrOverridesJsTypeMethod()
-            || method.isOrOverridesJsFunctionMethod()) {
-            /*
-             * SPECIAL: returning from this method passes a value from
-             * JavaScript into Java.
-             */
+        if (method.isNative()) {
+          // Returning from this method passes a value from JavaScript into Java.
           maybeRescueJavaScriptObjectPassingIntoJava(method.getType());
         }
-        if (method.isExported()
-            || method.isOrOverridesJsTypeMethod()
-            || method.isOrOverridesJsFunctionMethod()) {
+        if (method.isExported() || method.isJsTypeMember() || method.isJsFunctionMethod()) {
           for (JParameter param : method.getParams()) {
-            /**
-             * TODO (cromwellian): JS visible methods (virtual or static) may be supplied
-             * instantiated types that are implemented in JS. This code prevents parameters
-             * that are JS interface types from being considered un-instantiated. This logic
-             * needs to be tightened up here and elsewhere, we want to be conservative as
-             * possible to avoid code size bloat. Parameters in JsExport methods or
-             * JsType methods should not be pruned in order to keep the calling API
-             * consistent between optimized Java and JS.
-             */
+            // Parameters in JsExport, JsType, JsFunction methods should not be pruned in order to
+            // keep the API intact.
             rescue(param);
-            // Strings, Arrays, JSOs, and JsTypes can all be instantiatd in Javascript
-            if (canBeInstantiatedInJavaScript(param.getType())) {
-              // Param can be read from external JS if this method is implemented in JS
-              // this should really be done in rescueArgumentsIfParametersCanBeRead, but
-              // there is no JMethodCall to process since it might be from external JS
-              // arg supplied from external caller can be from JS, so treat type as instantiated
-              rescue((JReferenceType) param.getType(), true, true);
-            }
           }
         }
         rescueOverridingMethods(method);
         if (method == getClassMethod) {
           rescueClassLiteralsIfGetClassIsLive();
         }
-        if (program.isJsTypePrototype(method.getEnclosingType())) {
+        if (program.isJsTypePrototype(enclosingType)) {
           // for JsInterface Prototype methods, rescue all parameters
           // because these are stub methods and the parameters would get pruned ordinarily
           for (JParameter param : method.getParams()) {
@@ -656,7 +665,19 @@
       }
     }
 
-    private void rescue(JReferenceType type, boolean isReferenced, boolean isInstantiated) {
+    private void maybeRescueJsTypeArray(JType type) {
+      if (!(type instanceof JArrayType)) {
+        return;
+      }
+      JArrayType arrayType = (JArrayType) type;
+      if (program.typeOracle.isJsType(arrayType.getLeafType()) ||
+          program.typeOracle.isJsFunction(arrayType.getLeafType())) {
+        rescue(arrayType, true);
+        maybeRescueJsTypeArray(arrayType.getElementType());
+      }
+    }
+
+    private void rescue(JReferenceType type, boolean isInstantiated) {
       if (type == null) {
         return;
       }
@@ -673,7 +694,7 @@
         doVisit = true;
       }
 
-      if (isReferenced && referencedTypes.add(type)) {
+      if (referencedTypes.add(type)) {
         doVisit = true;
       }
 
@@ -682,42 +703,25 @@
       }
 
       accept(type);
-      if (type instanceof JDeclaredType) {
-          /*
-           * For @JsType, we rescue all JsType exposed fields and methods
-           * because we don't know if they'll be called from JS or not.
-           * That is, the Java implementor may be called because the interface
-           * was passed into JS, or it may be called via exported functions.
-           *
-           * For @JsFunction, we rescue its sam function because we don't know
-           * if they'll be called from JS or not. That is, when a instance of
-           * a @JsFunction is passed to JS, its sam function may be implicitly
-           * called by the instance.
-           *
-           * We may be able to tighten this to check for @JsExport as well,
-           * since if there is no @JsExport, the only way for JS code to get a
-           * reference to the interface is by it being constructed in Java
-           * and passed via JSNI into JS, and in that mechanism, the
-           * rescue would happen automatically.
-           */
-        JDeclaredType dtype = (JDeclaredType) type;
 
-        // On the surface this would appear to fail to correctly rescue any exported method that is
-        // on a subclass of a @JsType annotated type. But actually since such an exported method is
-        // an override and since it overrides something that has been rescued, such methods are
-        // already safely rescued by other ControlFlow logic.
-        if (dtype.isJsType() || dtype.isJsFunction()) {
-          for (JMethod method : dtype.getMethods()) {
-            if (method.isOrOverridesJsTypeMethod()
-                || method.isOrOverridesJsFunctionMethod()) {
-              rescue(method);
-            }
-          }
-          for (JField field : dtype.getFields()) {
-            if (field.isJsTypeMember()) {
-              rescue(field);
-            }
-          }
+      if (!program.typeOracle.isJsType(type) && !program.typeOracle.isJsFunction(type)) {
+        return;
+      }
+
+      /*
+       * We rescue all JsType member and JsFunction methods because we don't know if they'll be
+       * called from JS or not.
+       */
+      JDeclaredType declaredType = (JDeclaredType) type;
+
+      for (JMethod method : declaredType.getMethods()) {
+        if (method.isJsTypeMember() || method.isJsFunctionMethod()) {
+          rescue(method);
+        }
+      }
+      for (JField field : declaredType.getFields()) {
+        if (field.isJsTypeMember()) {
+          rescue(field);
         }
       }
     }
@@ -775,10 +779,6 @@
       }
     }
 
-    private void rescueAndInstantiate(JClassType type) {
-      rescue(type, true, true);
-    }
-
     /**
      * The code is very tightly tied to the behavior of
      * Pruner.CleanupRefsVisitor. CleanUpRefsVisitor will prune unread
@@ -787,11 +787,10 @@
      * need to iterate over Pruner until reaching a stable point, so we avoid
      * actually rescuing such arguments until/unless the parameter is read.
      */
-    private boolean rescueArgumentsIfParametersCanBeRead(JMethodCall call, JMethod method) {
-      if (call.getInstance() != null) {
-        // Explicitly visit instance since we're returning false below.
-        this.accept(call.getInstance());
-      }
+    private void rescueArgumentsIfParametersCanBeRead(JMethodCall call) {
+      JMethod method = call.getTarget();
+      assert !method.canBePolymorphic();
+
       List<JExpression> args = call.getArgs();
       List<JParameter> params = method.getParams();
       int i = 0;
@@ -799,9 +798,7 @@
         JExpression arg = args.get(i);
         JParameter param = params.get(i);
         if (arg.hasSideEffects() || liveFieldsAndMethods.contains(param)
-            // rescue any args of JsInterface Prototype methods or JsInterface
-            || method.isOrOverridesJsTypeMethod()
-            || method.isOrOverridesJsFunctionMethod()
+            // rescue any args of JsInterface Prototype methods
             || program.isJsTypePrototype(method.getEnclosingType())) {
           this.accept(arg);
           continue;
@@ -812,7 +809,6 @@
       for (int c = args.size(); i < c; ++i) {
         this.accept(args.get(i));
       }
-      return false;
     }
 
     /**
@@ -853,19 +849,21 @@
       }
     }
 
-    /**
-     * If the type is instantiable, rescue any of its virtual methods that a
-     * previously seen method call could call.
-     */
-    private void rescueMembersIfInstantiable(JDeclaredType type) {
-      if (!instantiatedTypes.contains(type)) {
-        return;
+    private void rescueMembersAndInstantiateSuperInterfaces(JDeclaredType type) {
+      for (JInterfaceType intfType : type.getImplements()) {
+        rescue(intfType, true);
       }
+      rescueMembers(type);
+    }
+
+    /**
+     * Rescues any of type's virtual methods that a previously seen method call could call.
+     */
+    private void rescueMembers(JDeclaredType type) {
+      assert instantiatedTypes.contains(type);
+
       for (JMethod method : type.getMethods()) {
-        if (!method.isStatic() && (membersToRescueIfTypeIsInstantiated.contains(method)
-            // method may be called from JS as well
-           || method.isOrOverridesJsTypeMethod()
-           || method.isOrOverridesJsFunctionMethod())) {
+        if (!method.isStatic() && membersToRescueIfTypeIsInstantiated.contains(method)) {
           rescue(method);
         }
       }
@@ -900,12 +898,13 @@
     }
   }
 
-  private boolean isTypeInstantiatedOrJso(JType type) {
+  private boolean isTypeInstantiatedOrJso(JDeclaredType type) {
     if (type == null) {
       return false;
     }
 
-    return type.isJsoType() || instantiatedTypes.contains(type);
+    return type.isJsoType() || type.isJsFunction() || type.isJsType()
+        || instantiatedTypes.contains(type);
   }
 
   /**
@@ -1010,20 +1009,6 @@
   }
 
   /**
-   * Forcibly rescue {@code typesToRescue}.
-   * <p>
-   * NOTE: this is used to rescue types that are made live by operations (e.g. casts) that
-   * have been eliminated by a normalization pass.
-   */
-  public void rescue(Iterable<JReferenceType> typesToRescue) {
-    // TODO(rluble): this functionality should go away, the AST should contain all the information
-    // needed to determine whether a type is live or not.
-    for (JReferenceType type : typesToRescue) {
-      rescuer.rescue(type, true, true);
-    }
-  }
-
-  /**
    * Specify the {@link DependencyRecorder} to be used for future traversals.
    * Specifying <code>null</code> means to stop recording dependencies.
    */
@@ -1058,13 +1043,13 @@
       for (JMethod method : type.getMethods()) {
         if (method.isExported()) {
           // treat class as instantiated, since a ctor may be called from JS export
-          rescuer.rescue(method.getEnclosingType(), true, true);
+          rescuer.rescue(method.getEnclosingType(), true);
           traverseFrom(method);
         }
       }
       for (JField field : type.getFields()) {
         if (field.isExported()) {
-          rescuer.rescue(field.getEnclosingType(), true, true);
+          rescuer.rescue(field.getEnclosingType(), true);
           rescuer.rescue(field);
         }
       }
@@ -1102,11 +1087,11 @@
    * execute as a result.
    */
   public void traverseFromInstantiationOf(JDeclaredType type) {
-    rescuer.rescue(type, true, true);
+    rescuer.rescue(type, true);
   }
 
   public void traverseFromReferenceTo(JDeclaredType type) {
-    rescuer.rescue(type, true, false);
+    rescuer.rescue(type, false);
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java b/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
index d7823c2..2aa55fd 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
@@ -31,7 +31,6 @@
 import com.google.gwt.dev.jjs.ast.JExpression;
 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.JLocal;
 import com.google.gwt.dev.jjs.ast.JLocalRef;
 import com.google.gwt.dev.jjs.ast.JMethod;
@@ -408,7 +407,7 @@
 
     @Override
     public boolean visit(JDeclaredType type, Context ctx) {
-      assert (referencedTypes.contains(type) || type instanceof JInterfaceType);
+      assert referencedTypes.contains(type);
       Predicate<JNode> notReferenced = Predicates.not(Predicates.in(referencedNonTypes));
       removeFields(notReferenced, type);
       removeMethods(notReferenced, type);
@@ -481,12 +480,9 @@
 
     @Override
     public boolean visit(JProgram program, Context ctx) {
-      for (JMethod method : program.getEntryMethods()) {
-        accept(method);
-      }
       for (Iterator<JDeclaredType> it = program.getDeclaredTypes().iterator(); it.hasNext();) {
         JDeclaredType type = it.next();
-        if (referencedTypes.contains(type) || program.typeOracle.isInstantiatedType(type)) {
+        if (referencedTypes.contains(type)) {
           accept(type);
         } else {
           prunedMethods.addAll(type.getMethods());
@@ -657,11 +653,6 @@
     OptimizerStats stats = new OptimizerStats(NAME);
 
     ControlFlowAnalyzer livenessAnalyzer = new ControlFlowAnalyzer(program);
-    // Don't prune JSOs, JsTypes that were considered instantiated before removing
-    // casts at {@link ImplementCastsAndTypeChecks}.
-    // TODO(rluble): the AST should have been left in a state that whatever method, attribute, etc
-    // from a JSO, JsType needs to be live, should have been already reachable from the AST.
-    livenessAnalyzer.rescue(program.typeOracle.getInstantiatedJsoTypesViaCast());
     livenessAnalyzer.setForPruning();
 
     // SPECIAL: Immortal codegen types are never pruned
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/EnumOrdinalizerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/EnumOrdinalizerTest.java
index 5e76689..6191146 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/EnumOrdinalizerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/EnumOrdinalizerTest.java
@@ -1138,7 +1138,6 @@
      */
     if (performCastReplacement) {
       ComputeCastabilityInformation.exec(program, false);
-      ComputeInstantiatedJsoInterfaces.exec(program);
       ImplementCastsAndTypeChecks.exec(program, false);
     }
     if (runEqualityNormalizer) {
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/FullCompileTestBase.java b/dev/core/test/com/google/gwt/dev/jjs/impl/FullCompileTestBase.java
index b2baba4..3f62f68 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/FullCompileTestBase.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/FullCompileTestBase.java
@@ -95,7 +95,6 @@
 
     optimizeJava();
     ComputeCastabilityInformation.exec(jProgram, false);
-    ComputeInstantiatedJsoInterfaces.exec(jProgram);
     ImplementCastsAndTypeChecks.exec(jProgram, false);
     ArrayNormalizer.exec(jProgram, false);
     TypeTightener.exec(jProgram);
diff --git a/dev/core/test/com/google/gwt/dev/js/JsStackEmulatorTest.java b/dev/core/test/com/google/gwt/dev/js/JsStackEmulatorTest.java
index ae0da86..7798933 100644
--- a/dev/core/test/com/google/gwt/dev/js/JsStackEmulatorTest.java
+++ b/dev/core/test/com/google/gwt/dev/js/JsStackEmulatorTest.java
@@ -39,7 +39,6 @@
 import com.google.gwt.dev.jjs.impl.ArrayNormalizer;
 import com.google.gwt.dev.jjs.impl.CatchBlockNormalizer;
 import com.google.gwt.dev.jjs.impl.ComputeCastabilityInformation;
-import com.google.gwt.dev.jjs.impl.ComputeInstantiatedJsoInterfaces;
 import com.google.gwt.dev.jjs.impl.FullCompileTestBase;
 import com.google.gwt.dev.jjs.impl.GenerateJavaScriptAST;
 import com.google.gwt.dev.jjs.impl.ImplementCastsAndTypeChecks;
@@ -281,7 +280,6 @@
 
     // These passes are needed by GenerateJavaScriptAST.
     ComputeCastabilityInformation.exec(jProgram, false);
-    ComputeInstantiatedJsoInterfaces.exec(jProgram);
     ImplementCastsAndTypeChecks.exec(jProgram, false);
     ArrayNormalizer.exec(jProgram, false);
 
diff --git a/user/test/com/google/gwt/core/client/interop/JsFunctionTest.java b/user/test/com/google/gwt/core/client/interop/JsFunctionTest.java
index 509f32e..8ea042a 100644
--- a/user/test/com/google/gwt/core/client/interop/JsFunctionTest.java
+++ b/user/test/com/google/gwt/core/client/interop/JsFunctionTest.java
@@ -198,6 +198,7 @@
     Object object = createFunction();
     assertTrue(object instanceof MyJsFunctionInterface);
     assertTrue(object instanceof MyJsFunctionIdentityInterface);
+    assertTrue(object instanceof MyJsFunctionWithOnlyInstanceofReference);
     assertFalse(object instanceof MyJsFunctionInterfaceImpl);
     assertTrue(object instanceof ElementLikeJsInterface);
   }
@@ -206,6 +207,7 @@
     Object object = createObject();
     assertFalse(object instanceof MyJsFunctionInterface);
     assertFalse(object instanceof MyJsFunctionIdentityInterface);
+    assertFalse(object instanceof MyJsFunctionWithOnlyInstanceofReference);
     assertFalse(object instanceof MyJsFunctionInterfaceImpl);
     assertTrue(object instanceof ElementLikeJsInterface);
   }
@@ -215,6 +217,7 @@
     assertTrue(object instanceof MyJsFunctionInterface);
     assertTrue(object instanceof MyJsFunctionInterfaceImpl);
     assertTrue(object instanceof MyJsFunctionIdentityInterface);
+    assertTrue(object instanceof MyJsFunctionWithOnlyInstanceofReference);
     assertTrue(object instanceof ElementLikeJsInterface);
   }
 
diff --git a/user/test/com/google/gwt/core/client/interop/JsTypeArrayTest.java b/user/test/com/google/gwt/core/client/interop/JsTypeArrayTest.java
index 113c2eb..fecc5c5 100644
--- a/user/test/com/google/gwt/core/client/interop/JsTypeArrayTest.java
+++ b/user/test/com/google/gwt/core/client/interop/JsTypeArrayTest.java
@@ -49,8 +49,7 @@
     @JsProperty int getId();
   }
 
-  // TODO: Fix CFA
-  public void _disabled_testJsTypeArray_returnFromNativeWithACall() {
+  public void testJsTypeArray_returnFromNativeWithACall() {
     SimpleJsTypeReturnFromNativeWithACall[] array = returnJsTypeWithIdsFromNative();
     assertEquals(2, array[1].getId());
   }
@@ -67,8 +66,8 @@
     public SimpleJsTypeAsAField[] arrayField;
   }
 
-  // TODO: Fix CFA
-  public void _disabled_testJsTypeArray_asAField() {
+  // TODO(rluble): Needs fixes in ImlementCastsAndTypeChecks, ArrayNormalizer and maybe type oracle.
+  public void __disabled__testJsTypeArray_asAField() {
     SimpleJsTypeAsAFieldHolder holder = new SimpleJsTypeAsAFieldHolder();
     fillArrayField(holder);
     SimpleJsTypeAsAField[] array = holder.arrayField;
@@ -109,8 +108,8 @@
     @JsProperty int getId();
   }
 
-  // TODO: Fix CFA
-  public void _disabled_testJsType3DimArray_castedFromNativeWithACall() {
+  // TODO(rluble): Needs fixes in ImlementCastsAndTypeChecks, ArrayNormalizer and maybe type oracle.
+  public void __disabled__testJsType3DimArray_castedFromNativeWithACall() {
     SimpleJsTypeReturnForMultiDimArray[][][] array =
         (SimpleJsTypeReturnForMultiDimArray[][][]) returnJsType3DimFromNative();
     assertEquals(1, array.length);
@@ -123,16 +122,14 @@
     return [ [ [{id:1}, {id:2}, {}], [] ] ];
   }-*/;
 
-  // TODO: Fix CFA
-  public void _disabled_testString3DimArray_castedFromNative() {
-    String[][][] array = (String[][][]) returnString3DimFromNative();
-    assertEquals(1, array.length);
-    assertEquals(2, array[0].length);
-    assertEquals(3, array[0][0].length);
-    assertEquals("1", array[0][0][0]);
+  // TODO(rluble): Needs fixes in ImlementCastsAndTypeChecks, ArrayNormalizer and maybe type oracle.
+  public void __disabled__testObjectArray_castedFromNative() {
+    Object[] array = (Object[]) returnObjectArrayFromNative();
+    assertEquals(3, array.length);
+    assertEquals("1", array[0]);
   }
 
-  private native Object returnString3DimFromNative() /*-{
-    return [ [ ["1", "2", "3"], [] ] ];
+  private native Object returnObjectArrayFromNative() /*-{
+    return ["1", "2", "3"];
   }-*/;
 }
diff --git a/user/test/com/google/gwt/core/client/interop/MyJsFunctionWithOnlyInstanceofReference.java b/user/test/com/google/gwt/core/client/interop/MyJsFunctionWithOnlyInstanceofReference.java
new file mode 100644
index 0000000..4c5b951
--- /dev/null
+++ b/user/test/com/google/gwt/core/client/interop/MyJsFunctionWithOnlyInstanceofReference.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2015 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.core.client.interop;
+
+import com.google.gwt.core.client.js.JsFunction;
+
+/**
+ * A functional interface annotated by JsFunction that is only referenced by instanceof.
+ */
+@JsFunction
+public interface MyJsFunctionWithOnlyInstanceofReference {
+  int foo(int a);
+}
diff --git a/user/test/com/google/gwt/dev/jjs/test/JsoTest.java b/user/test/com/google/gwt/dev/jjs/test/JsoTest.java
index 64ef561..d6a5f4a 100644
--- a/user/test/com/google/gwt/dev/jjs/test/JsoTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/JsoTest.java
@@ -533,6 +533,15 @@
     }
   }
 
+  static class MyJSO extends JavaScriptObject {
+    protected MyJSO() {
+    }
+
+    public final boolean equalMethod(Object o) {
+      return this == o;
+    }
+  }
+
   public void testEquality() {
     JavaScriptObject jso = makeJSO();
     assertEquals(jso, jso);
@@ -543,6 +552,11 @@
 
     jso2 = returnMe(jso);
     assertEquals(jso, jso2);
+
+    MyJSO jso3 = (MyJSO) makeJSO();
+    MyJSO jso4 = (MyJSO) makeJSO();
+    assertTrue(jso3.equalMethod(jso3));
+    assertFalse(jso3.equalMethod(jso4));
   }
 
   public void testGenericsJsos() {