Rescue types represented as natives when supertypes cross JS boundaries.

Bug: #9482
Bug-Link: https://github.com/gwtproject/gwt/issues/9482
Change-Id: I81df1a665a843d0cf11334c6ef47f6490d4b8291
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 aa4e865..8599040 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
@@ -60,8 +60,10 @@
 import com.google.gwt.dev.js.ast.JsNameRef;
 import com.google.gwt.dev.js.ast.JsVisitor;
 import com.google.gwt.thirdparty.guava.common.collect.ArrayListMultimap;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableMultimap;
 import com.google.gwt.thirdparty.guava.common.collect.ListMultimap;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
+import com.google.gwt.thirdparty.guava.common.collect.Multimap;
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
 
 import java.util.List;
@@ -517,10 +519,11 @@
     }
 
     private boolean canBeInstantiatedInJavaScript(JType 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.
+      // Technically, JsType/JsFunction are also instantiable in JavaScript but we don't track them
+      // like a JSO. The JSO liveness tracking mechanism has very quirky semantics and using it to
+      // track JsType/JsFunctions might result on incorrect over-pruning.
       if (program.typeOracle.canBeJavaScriptObject(type)
-          || program.isRepresentedAsNativeJsPrimitive(type)) {
+          || representedAsNativeTypesBySupertype.containsKey(type.getUnderlyingType())) {
         return true;
       }
 
@@ -596,10 +599,16 @@
       if (!canBeInstantiatedInJavaScript(type)) {
         return;
       }
-      rescue((JReferenceType) type, true);
+
+      JReferenceType underlyingType = (JReferenceType) type.getUnderlyingType();
+      for (JReferenceType representedAsNativeType :
+          representedAsNativeTypesBySupertype.get(underlyingType)) {
+        rescue(representedAsNativeType, true);
+      }
+      rescue(underlyingType, true);
       if (program.typeOracle.isSingleJsoImpl(type)) {
         // Cast of JSO into SingleJso interface, rescue the implementor if exists
-        JClassType singleJsoImpl = program.typeOracle.getSingleJsoImpl((JReferenceType) type);
+        JClassType singleJsoImpl = program.typeOracle.getSingleJsoImpl(underlyingType);
         rescue(singleJsoImpl, true);
       }
     }
@@ -935,6 +944,7 @@
   private final RescueVisitor rescuer;
   private final JMethod runAsyncOnSuccess;
   private JMethod stringValueOfChar = null;
+  private final Multimap<JType, JDeclaredType> representedAsNativeTypesBySupertype;
 
   public ControlFlowAnalyzer(ControlFlowAnalyzer cfa) {
     program = cfa.program;
@@ -955,15 +965,26 @@
           ArrayListMultimap.create(cfa.argumentsToRescueIfParameterRead);
     }
     rescuer = new RescueVisitor();
+    representedAsNativeTypesBySupertype = cfa.representedAsNativeTypesBySupertype;
   }
 
-  public ControlFlowAnalyzer(JProgram program) {
+  public ControlFlowAnalyzer(final JProgram program) {
     this.program = program;
     asyncFragmentOnLoad = program.getIndexedMethod(RuntimeConstants.ASYNC_FRAGMENT_LOADER_ON_LOAD);
     runAsyncOnSuccess = program.getIndexedMethod(RuntimeConstants.RUN_ASYNC_CALLBACK_ON_SUCCESS);
     getClassField = program.getIndexedField(RuntimeConstants.OBJECT_CLAZZ);
     getClassMethod = program.getIndexedMethod(RuntimeConstants.OBJECT_GET_CLASS);
     rescuer = new RescueVisitor();
+
+    ImmutableMultimap.Builder<JType, JDeclaredType> representedAsNativeTypeBySuperTypeBuilder =
+        ImmutableMultimap.builder();
+    for (JDeclaredType type : program.getRepresentedAsNativeTypes()) {
+      representedAsNativeTypeBySuperTypeBuilder.put(type, type);
+      for (JDeclaredType superType : JjsUtils.getSupertypes(type)) {
+        representedAsNativeTypeBySuperTypeBuilder.put(superType, type);
+      }
+    }
+    representedAsNativeTypesBySupertype = representedAsNativeTypeBySuperTypeBuilder.build();
   }
 
   /**
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 425d876..61ade42 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
@@ -73,11 +73,13 @@
 import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
 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;
 
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * General utilities related to Java AST manipulation.
@@ -569,6 +571,30 @@
   }
 
   /**
+   * Gets all the supertypes of {@code type}.
+   */
+
+  public static Set<JDeclaredType> getSupertypes(JDeclaredType type) {
+    Set<JDeclaredType> superTypes = Sets.newHashSet();
+    addAllSuperTypes(type, superTypes);
+    return superTypes;
+  }
+
+  /**
+   * Adds all the supertypes of {@code type} to {@code types}.
+   */
+  public static void addAllSuperTypes(JDeclaredType type, Set<JDeclaredType> types) {
+    if (type.getSuperClass() != null) {
+      types.add(type.getSuperClass());
+      addAllSuperTypes(type.getSuperClass(), types);
+    }
+    for (JInterfaceType interfaceType : type.getImplements()) {
+      types.add(interfaceType);
+      addAllSuperTypes(interfaceType, types);
+    }
+  }
+
+  /**
    * Returns the JsConstructor for a class or null if it does not have any.
    */
   public static JConstructor getJsConstructor(JDeclaredType type) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JsniRestrictionChecker.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JsniRestrictionChecker.java
index 8646b71..3bfffc3 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JsniRestrictionChecker.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JsniRestrictionChecker.java
@@ -17,7 +17,6 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.jjs.ast.Context;
 import com.google.gwt.dev.jjs.ast.JDeclaredType;
-import com.google.gwt.dev.jjs.ast.JInterfaceType;
 import com.google.gwt.dev.jjs.ast.JMethod;
 import com.google.gwt.dev.jjs.ast.JMethodBody;
 import com.google.gwt.dev.jjs.ast.JProgram;
@@ -49,7 +48,7 @@
       throws UnableToCompleteException {
     final Set<JDeclaredType> typesRequiringTrampolineDispatch = Sets.newHashSet();
     for (JDeclaredType type : program.getRepresentedAsNativeTypes()) {
-      collectAllSuperTypes(type, typesRequiringTrampolineDispatch);
+      JjsUtils.addAllSuperTypes(type, typesRequiringTrampolineDispatch);
     }
     new JVisitor() {
       @Override
@@ -152,17 +151,6 @@
     }
   }
 
-  private static void collectAllSuperTypes(JDeclaredType type, Set<JDeclaredType> allSuperTypes) {
-    if (type.getSuperClass() != null) {
-      allSuperTypes.add(type.getSuperClass());
-      collectAllSuperTypes(type.getSuperClass(), allSuperTypes);
-    }
-    for (JInterfaceType interfaceType : type.getImplements()) {
-      allSuperTypes.add(interfaceType);
-      collectAllSuperTypes(interfaceType, allSuperTypes);
-    }
-  }
-
   private static boolean isNonStaticJsoClassDispatch(JMethod method, JDeclaredType enclosingType) {
     return !method.isStatic() && enclosingType.isJsoType();
   }
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzerTest.java
index 2f26cb5..c9f92af 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzerTest.java
@@ -61,7 +61,7 @@
       Set<JType> expectedSet = Sets.newHashSet();
       for (String expectedType : expectedTypes) {
         JType type = findType(program, expectedType);
-        assertNotNull(type);
+        assertNotNull("Type " + expectedType + " not instantiated.", type);
         expectedSet.add(type);
       }
       assertEquals(expectedSet, cfa.getInstantiatedTypes());
@@ -264,8 +264,9 @@
     );
     analyzeSnippet("Test.field.hashCode();").assertOnlyInstantiatedTypes(
         "JsoIntf", "SingleJso", "JavaScriptObject", "Object",
-        // These are all live because of the method Test.toString can be referenced externally.
-        "String", "Comparable", "CharSequence", "Serializable");
+        // These are all live because of the method Test.toString and equals can be referenced
+        // externally
+        "String", "Comparable", "CharSequence", "Serializable", "Boolean", "Number", "Double");
 
     addOrReplaceResource("test.Test",
         "package test;",
@@ -277,8 +278,42 @@
     );
     analyzeSnippet("Test.field.hashCode();").assertOnlyInstantiatedTypes(
         "JsoIntf", "SingleJso", "JavaScriptObject", "Object",
-        // These are all live because of the method Test.toString can be referenced externally.
+        // These are all live because of the method Test.toString and equals can be referenced
+        // externally
+        "String", "Comparable", "CharSequence", "Serializable", "Boolean", "Number", "Double");
+  }
+
+  /**
+   * Tests that the JavaScriptObject type gets rescued in the three specific
+   * circumstances where values can pass from JS into Java.
+   */
+  public void testRescueRepresentedAsNative() throws Exception {
+    addOrReplaceResource("test.Test",
+        "package test;",
+        "import jsinterop.annotations.JsMethod;",
+        "public class Test {",
+        "  @JsMethod",
+        "  public static native Object getObject();",
+        "  @JsMethod",
+        "  public static native Number getNumber();",
+        "  @JsMethod",
+        "  public static native Comparable getComparable();",
+        "  @JsMethod",
+        "  public static native Double getDouble();",
+        "}"
+    );
+
+    addSnippetImport("test.Test");
+
+    analyzeSnippet("Test.getObject();").assertOnlyInstantiatedTypes(
+        "Object", "Boolean", "Number", "Double",
         "String", "Comparable", "CharSequence", "Serializable");
+    analyzeSnippet("Test.getNumber();").assertOnlyInstantiatedTypes(
+        "Object", "Number", "Double", "Serializable");
+    analyzeSnippet("Test.getComparable();").assertOnlyInstantiatedTypes(
+        "Object", "String", "Comparable", "CharSequence", "Serializable");
+    analyzeSnippet("Test.getDouble();").assertOnlyInstantiatedTypes(
+        "Object", "Number", "Double", "Serializable");
   }
 
   private void addOrReplaceResource(String qualifiedTypeName, final String... source) {