Fix implementation of Object methods invoked on native JsTypes.

Object methods on native JsTypes are handled by JSO trampolines.
For that to work corretly, we make sure that we never tighten a
method call into a native JsMethod. There are no performance
benefits of doing so, and it interferes with the hacky way we
are handling these overrides.

Change-Id: I522cf60ae6a0077c0c009fc4e081c75e5c85acb1
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 f9fe8fe..573b563 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
@@ -488,15 +488,15 @@
     if (type.isArrayType()) {
       // A variable of type Object[] could contain an instance of native JsType[], the latter
       // is treated as a JSO for devirtualization purposes.
-      // TODO(rluble): maybe it should not be treated as a JSO, think .toString().
       return EnumSet.of(DispatchType.JSO, DispatchType.JAVA_ARRAY);
     }
     EnumSet<DispatchType> dispatchSet = EnumSet.noneOf(DispatchType.class);
     DispatchType dispatchType = getRepresentedAsNativeTypesDispatchMap().get(type);
     if (dispatchType != null) {
       dispatchSet = EnumSet.of(dispatchType);
-    } else if (typeOracle.isDualJsoInterface(type)) {
-      // If it is an interface implemented both by JSOs and regular Java Objects;
+    } else if (typeOracle.isDualJsoInterface(type) || type.isJsNative()) {
+      // If it is an interface implemented both by JSOs and regular Java Objects; native JsTypes
+      // are considered JSOs for object method devirtualization.
       dispatchSet = EnumSet.of(DispatchType.HAS_JAVA_VIRTUAL_DISPATCH, DispatchType.JSO);
     } else if (typeOracle.isSingleJsoImpl(type) || type.isJsoType()) {
       // If it is either an interface implemented by JSOs or JavaScriptObject or one of its
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java
index a1f3fb0..bd1e20f 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java
@@ -203,6 +203,10 @@
       if (method.isJsOverlay()) {
         return true;
       }
+      if (method.getEnclosingType().isJsNative()) {
+        // Methods in a native JsType that are not JsOverlay should NOT be devirtualized.
+        return false;
+      }
       EnumSet<DispatchType> dispatchType = program.getDispatchType(instanceType);
       dispatchType.remove(DispatchType.HAS_JAVA_VIRTUAL_DISPATCH);
       return !dispatchType.isEmpty();
@@ -434,7 +438,7 @@
     // Decide where to place the devirtual method. Ideally these methods should reside in the
     // declaring type, but some of these will be interfaces and currently GWT does not emit
     // any code for them.
-    // TODO(rluble): place interface methods in the corresponding interface once Java 9 defender
+    // TODO(rluble): place interface methods in the corresponding interface once Java 8 defender
     // method support is implemented.
     JClassType devirtualMethodEnclosingClass  = null;
     if (method.getEnclosingType() instanceof JClassType) {
@@ -455,7 +459,7 @@
         devirtualMethodEnclosingClass = (JClassType)
             dispatchToMethodByTargetType.get(DispatchType.JSO).getEnclosingType();
       } else {
-        // It is an interface implemented by String or arrays, place it in Object.
+        // It is an interface implemented by devirtualized types, place it in Object.
         devirtualMethodEnclosingClass = program.getTypeJavaLangObject();
       }
     }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
index 1e4e0a4..02305f3 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
@@ -2915,7 +2915,9 @@
       JMethod method = type.getMethods().get(GET_CLASS_METHOD_INDEX);
       assert (GET_CLASS_METHOD_NAME.equals(method.getName()));
       SourceInfo info = method.getSourceInfo();
-      if (type.isJsoType()) {
+      if (type.isJsoType() || type.isJsNative()) {
+        // Native types and JSOs get a synthetic get class that return JavaScriptObject.class.
+        //
         // return Cast.getClass(this)
         JjsUtils.replaceMethodBody(method,
             new JMethodCall(info, null, CAST_GET_CLASS_METHOD, new JThisRef(info, type)));
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodCallTightener.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodCallTightener.java
index bc497cf..96c9de4 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodCallTightener.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodCallTightener.java
@@ -124,7 +124,10 @@
 
       JMethod mostSpecificOverride =
           program.typeOracle.findMostSpecificOverride(underlyingType, original);
-      if (mostSpecificOverride == original) {
+
+      if (mostSpecificOverride == original || mostSpecificOverride.isJsNative()) {
+        // Do not tighten if the target method is a native JsMethod; this is necessary for the
+        // JSO trampoline to be invoked on getClass().
         return methodCall;
       }
       JMethodCall newCall = new JMethodCall(
diff --git a/user/test/com/google/gwt/core/CoreJsInteropSuite.java b/user/test/com/google/gwt/core/CoreJsInteropSuite.java
index 4285000..55602f5 100644
--- a/user/test/com/google/gwt/core/CoreJsInteropSuite.java
+++ b/user/test/com/google/gwt/core/CoreJsInteropSuite.java
@@ -22,6 +22,7 @@
 import com.google.gwt.core.interop.JsTypeArrayTest;
 import com.google.gwt.core.interop.JsTypeBridgeTest;
 import com.google.gwt.core.interop.JsTypeTest;
+import com.google.gwt.core.interop.NativeJsTypeTest;
 
 import junit.framework.Test;
 import junit.framework.TestSuite;
@@ -40,6 +41,7 @@
     suite.addTestSuite(JsMethodTest.class);
     suite.addTestSuite(JsTypeArrayTest.class);
     suite.addTestSuite(JsFunctionTest.class);
+    suite.addTestSuite(NativeJsTypeTest.class);
 
     return suite;
   }
diff --git a/user/test/com/google/gwt/core/interop/NativeJsTypeTest.java b/user/test/com/google/gwt/core/interop/NativeJsTypeTest.java
index 739ce38..3dd9382 100644
--- a/user/test/com/google/gwt/core/interop/NativeJsTypeTest.java
+++ b/user/test/com/google/gwt/core/interop/NativeJsTypeTest.java
@@ -33,7 +33,7 @@
 
   @JsType(isNative = true)
   static class MyNativeJsType {
-    // TODO(rluble): these methods should be synthesyzed by the compiler.
+    // TODO(rluble): these methods should be synthesized by the compiler.
     @Override
     public native String toString();
     @Override