Fix incorrect pruning of overridden JsMethods.

JsMember information is inherited from overriden methods
and pruning any of these overridden methods might cause the
compiler to "forget" that a JsMethod/JsProperty is a
JsMethod/JsProperty.

This is the correct behaviour in most cases (as only if
-nogenerateJsInteropExports those overriden members are pruned),
except for the case of native JsMethods.

This patch makes methods and fields that can be implemented
externally live.

Bug: #9358
Bug-Link: http://github.com/gwtproject/gwt/issues/9358
Change-Id: I80637883849af5d99542f8d0bc35c3e5b895a9e1
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 e4cf46a..1299734 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
@@ -577,11 +577,10 @@
     }
 
     /**
-     * Subclasses of JavaScriptObject are never instantiated directly. They are
-     * implicitly created when a JSNI method passes a reference to an existing
-     * JS object into Java code. If any point in the program can pass a value
-     * from JS into Java which could potentially be cast to JavaScriptObject, we
-     * must rescue JavaScriptObject.
+     * Subclasses of JavaScriptObject are never instantiated directly. They are implicitly created
+     * when a JSNI method passes a reference to an existing JS object into Java code. If any point
+     * in the program can pass a value from JS into Java which could potentially be cast to
+     * JavaScriptObject, we must rescue JavaScriptObject.
      *
      * @param type The type of the value passing from Java to JavaScript.
      * @see com.google.gwt.core.client.JavaScriptObject
@@ -760,12 +759,11 @@
     }
 
     /**
-     * The code is very tightly tied to the behavior of
-     * Pruner.CleanupRefsVisitor. CleanUpRefsVisitor will prune unread
-     * parameters, and also prune any matching arguments that don't have side
-     * effects. We want to make control flow congruent to pruning, to avoid the
-     * need to iterate over Pruner until reaching a stable point, so we avoid
-     * actually rescuing such arguments until/unless the parameter is read.
+     * The code is very tightly tied to the behavior of Pruner.CleanupRefsVisitor.
+     * CleanUpRefsVisitor will prune unread parameters, and also prune any matching arguments that
+     * don't have side effects. We want to make control flow congruent to pruning, to avoid the need
+     * to iterate over Pruner until reaching a stable point, so we avoid actually rescuing such
+     * arguments until/unless the parameter is read.
      */
     private void rescueArgumentsIfParametersCanBeRead(JMethodCall call) {
       JMethod method = call.getTarget();
@@ -847,15 +845,14 @@
       }
       for (JField field : type.getFields()) {
         if (!field.isStatic() && membersToRescueIfTypeIsInstantiated.contains(field)) {
-            rescue(field);
+          rescue(field);
         }
       }
     }
 
     /**
-     * Assume that <code>method</code> is live. Rescue any overriding methods
-     * that might be called if <code>method</code> is called through virtual
-     * dispatch.
+     * Assume that <code>method</code> is live. Rescue any overriding methods that might be called
+     * if <code>method</code> is called through virtual dispatch.
      */
     private void rescueOverridingMethods(JMethod method) {
       if (method.isStatic()) {
@@ -1028,14 +1025,14 @@
 
       // first time through, record all exported methods
       for (JMethod method : type.getMethods()) {
-        if (method.isJsInteropEntryPoint()) {
+        if (method.isJsInteropEntryPoint() || method.canBeImplementedExternally()) {
           // treat class as instantiated, since a ctor may be called from JS export
           rescuer.rescue(method.getEnclosingType(), true);
           traverseFrom(method);
         }
       }
       for (JField field : type.getFields()) {
-        if (field.isJsInteropEntryPoint()) {
+        if (field.isJsInteropEntryPoint() || field.canBeImplementedExternally()) {
           rescuer.rescue(field.getEnclosingType(), true);
           rescuer.rescue(field);
         }
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 27f6879..7415529 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
@@ -526,8 +526,12 @@
     for (JParameter param : exampleMethod.getParams()) {
       emptyMethod.cloneParameter(param);
     }
-    JMethodBody body = new JMethodBody(exampleMethod.getSourceInfo());
-    emptyMethod.setBody(body);
+    // If the enclosing type is native, make sure the synthetic empty method is native by leaving
+    // the body = null.
+    if (!inType.isJsNative()) {
+      JMethodBody body = new JMethodBody(exampleMethod.getSourceInfo());
+      emptyMethod.setBody(body);
+    }
     emptyMethod.freezeParamTypes();
     inType.addMethod(emptyMethod);
     return emptyMethod;
diff --git a/user/test/com/google/gwt/dev/jjs/test/CompilerMiscRegressionTest.java b/user/test/com/google/gwt/dev/jjs/test/CompilerMiscRegressionTest.java
index 0fdbbc3..26ca07c 100644
--- a/user/test/com/google/gwt/dev/jjs/test/CompilerMiscRegressionTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/CompilerMiscRegressionTest.java
@@ -35,6 +35,8 @@
 import java.util.Map;
 
 import javaemul.internal.annotations.DoNotInline;
+import jsinterop.annotations.JsPackage;
+import jsinterop.annotations.JsProperty;
 import jsinterop.annotations.JsType;
 
 /**
@@ -343,4 +345,28 @@
       assertEquals("Array mismatch at element " + i , expected[i], actual[i]);
     }
   }
+
+  @JsType(isNative = true)
+  interface SomeNativeInterface {
+    @JsProperty
+    String getTextContent();
+  }
+
+  @JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object")
+  static abstract class AbstractNativeType implements SomeNativeInterface {
+  }
+
+  private static native AbstractNativeType createAbstractNativeType(String value) /*-{
+    return {textContent : value};
+  }-*/;
+
+  // Tests that methods that override native JsMethods are generated properly w.r.t
+  // JsMethod/JsProperty annotations even if the overridden method is not live.
+  // See bug #9358
+  //
+  // Make sure this method is part of a suite that is run with -nogenerateJsInteropExports.
+  public void testNativeJsMethodDispatch_unreferencedSupertypeMethod() {
+    final AbstractNativeType o = createAbstractNativeType("Hello");
+    assertEquals("Hello", o.getTextContent());
+  }
 }