Fix ArrayStoreException in assignments to an element of an array of a single jso interface type.

Addresses issue 6448

Review at http://gwt-code-reviews.appspot.com/1470801


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10414 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ArrayNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ArrayNormalizer.java
index 6fe6034..3e25125 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ArrayNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ArrayNormalizer.java
@@ -104,7 +104,21 @@
       if (elementType instanceof JReferenceType) {
         JReferenceType elementRefType = (JReferenceType) elementType;
         elementType = elementRefType.getUnderlyingType();
+        if (program.typeOracle.isEffectivelyJavaScriptObject(elementRefType)) {
+          /*
+           * treat types that are effectively JSO's as JSO's, for the purpose of
+           * castability checking
+           */
+          elementRefType = program.getJavaScriptObject();
+        }  
         elementQueryId = program.getQueryId(elementRefType);
+        if (program.typeOracle.isDualJsoInterface(elementRefType)) {
+          /*
+           * invert the queryId, to indicate dual castability for JSO's and the
+           * Java type represented by the inverse of the queryId
+           */
+          elementQueryId *= -1;
+        }
       }
       return new JsQueryType(sourceInfo, elementType, elementQueryId);
     }
diff --git a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Array.java b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Array.java
index 348e5da..60c809f 100644
--- a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Array.java
+++ b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Array.java
@@ -180,14 +180,46 @@
   }
 
   /**
-   * Performs an array assignment, checking for valid index and type.
+   * Performs an array assignment, after validating the type of the value being
+   * stored. The form of the type check depends on the value of queryId, as
+   * follows:
+   * <p>
+   * If the queryId is > 0, this indicates a normal cast check should be
+   * performed, using the queryId as the cast destination type.
+   * JavaScriptObjects cannot be stored in this case.
+   * <p>
+   * If the queryId == 0, this is the cast target for the Object type, in which
+   * case all types can be stored, including JavaScriptObject.
+   * <p>
+   * If the queryId == -1, this indicates that only JavaScriptObjects can be
+   * stored (-1 is the cast target for JavaScriptObject, by convention).
+   * <p>
+   * If the queryId is < -1, this indicates that both JavaScriptObjects, and
+   * Java types can be stored. In the case of Java types, the inverse of the
+   * queryId is used for castability testing. This case is provided to support
+   * arrays declared with an interface type, which has dual implementations
+   * (i.e. interface types which have both Java and JavaScriptObject
+   * implementations).
+   * <p>
+   * Note, by convention, a queryId of 1 is reserved for String, which is a
+   * final class, and can't implement an interface, and thus, it's inverse, -1,
+   * can safely be interpreted as a special case, as stated above.
+   * <p>
+   * Attempting to store an object that cannot satisfy the castability check
+   * throws an {@link ArrayStoreException}.
    */
   public static Object setCheck(Array array, int index, Object value) {
     if (value != null) {
       if (array.queryId > 0 && !Cast.canCastUnsafe(value, array.queryId)) {
+        // value must be castable to queryId
         throw new ArrayStoreException();
-      }
-      if (array.queryId < 0 && Cast.isJavaObject(value)) {
+      } else if (array.queryId == -1 && Cast.isJavaObject(value)) {
+        // value must be a JavaScriptObject
+        throw new ArrayStoreException();
+      } else if (array.queryId < -1 && !Cast.isJavaScriptObject(value)
+          && !Cast.canCastUnsafe(value, -array.queryId)) {
+        // value must be a JavaScriptObject, or else castable to the inverse of
+        // queryId
         throw new ArrayStoreException();
       }
     }
@@ -283,9 +315,10 @@
   protected Class<?> arrayClass = null;
 
   /**
-   * The necessary cast target for objects stored into this array. Attempting to
-   * store an object that cannot satisfy the query id throws and
-   * {@link ArrayStoreException}.
+   * A representation of the necessary cast target for objects stored into this
+   * array.
+   * 
+   * @see #setCheck
    */
   protected int queryId = 0;
 }
diff --git a/user/test/com/google/gwt/dev/jjs/test/SingleJsoImplTest.java b/user/test/com/google/gwt/dev/jjs/test/SingleJsoImplTest.java
index ae07751..9381aa8 100644
--- a/user/test/com/google/gwt/dev/jjs/test/SingleJsoImplTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/SingleJsoImplTest.java
@@ -442,6 +442,28 @@
   }
 
   /**
+   * An interface that has dual JSO and non-JSO implementations.
+   */
+  interface DualSimple {
+    String a();
+  }
+
+  static class JavaDualSimple implements DualSimple {
+    public String a() {
+      return "object";
+    }
+  }
+
+  static final class JsoDualSimple extends JavaScriptObject implements DualSimple {
+    protected JsoDualSimple() {
+    }
+
+    public String a() {
+      return "jso";
+    }
+  }
+
+  /**
    * Ensure that a Java-only implementation of a SingleJsoImpl interface works.
    */
   static class SimpleOnlyJava implements SimpleOnlyJavaInterface {
@@ -514,6 +536,115 @@
     return "com.google.gwt.dev.jjs.CompilerSuite";
   }
 
+  /*
+   * These "testAssign*" tests below are inspired by the issue reported here:
+   * 
+   * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=6448
+   */
+  public void testAssignToDualJavaJsoImplInterfaceArray() {
+    int i = 0;
+    DualSimple[] dualSimples = new DualSimple[10];
+
+    DualSimple dualSimple = (DualSimple) JavaScriptObject.createObject();
+    dualSimples[i] = dualSimple;
+    assertEquals("jso", dualSimples[i++].a());
+
+    JsoDualSimple jsoDualSimple = (JsoDualSimple) JavaScriptObject.createObject();
+    dualSimples[i] = jsoDualSimple;
+    assertEquals("jso", dualSimples[i++].a());
+
+    DualSimple javaDualSimple = new JavaDualSimple();
+    dualSimples[i] = javaDualSimple;
+    assertEquals("object", dualSimples[i++].a());
+
+    Object[] objects = dualSimples;
+    objects[i++] = dualSimple;
+    objects[i++] = jsoDualSimple;
+    objects[i++] = javaDualSimple;
+    try {
+      objects[i++] = new Object();
+      fail("ArrayStoreException expected");
+    } catch (ArrayStoreException expected) {
+    }
+  }
+
+  public void testAssignToJsoArray() {
+    int i = 0;
+    JsoDualSimple[] jsoDualSimples = new JsoDualSimple[10];
+
+    JsoDualSimple jsoDualSimple = (JsoDualSimple) JavaScriptObject.createObject();
+    jsoDualSimples[i] = jsoDualSimple;
+    assertEquals("jso", jsoDualSimples[i++].a());
+
+    DualSimple dualSimple = (DualSimple) JavaScriptObject.createObject();
+    jsoDualSimples[i] = (JsoDualSimple) dualSimple;
+    assertEquals("jso", jsoDualSimples[i++].a());
+
+    DualSimple[] dualSimples = jsoDualSimples;
+    try {
+      DualSimple javaDualSimple = new JavaDualSimple();
+      dualSimples[i++] = javaDualSimple;
+      fail("ArrayStoreException expected");
+    } catch (ArrayStoreException expected) {
+    }
+  }
+
+  public void testAssignToJavaArray() {
+    int i = 0;
+    JavaDualSimple[] javaDualSimples = new JavaDualSimple[10];
+
+    JavaDualSimple javaDualSimple = new JavaDualSimple();
+    javaDualSimples[i] = javaDualSimple;
+    assertEquals("object", javaDualSimples[i++].a());
+
+    DualSimple[] dualSimples = javaDualSimples;
+    try {
+      DualSimple jsoDualSimple = (DualSimple) JavaScriptObject.createObject();
+      dualSimples[i++] = jsoDualSimple;
+      fail("ArrayStoreException expected");
+    } catch (ArrayStoreException expected) {
+    }
+  }
+
+  public void testAssignToObjectArray() {
+    int i = 0;
+    Object[] objects = new Object[10];
+
+    Simple simple = (Simple) JavaScriptObject.createObject();
+    objects[i++] = simple;
+
+    JsoSimple jsoSimple = (JsoSimple) JavaScriptObject.createObject();
+    objects[i++] = jsoSimple;
+
+    JavaScriptObject jso = JavaScriptObject.createObject();
+    objects[i++] = jso;
+
+    Object object = new Object();
+    objects[i++] = object;
+
+    Integer integer = new Integer(1);
+    objects[i++] = integer;
+  }
+
+  public void testAssignToSingleJsoImplInterfaceArray() {
+    int i = 0;
+    Simple[] simples = new Simple[10];
+    Simple simple = (Simple) JavaScriptObject.createObject();
+    simples[i] = simple;
+    assertEquals("a", simples[i++].a());
+
+    Simple jsoSimple = (JsoSimple) JavaScriptObject.createObject();
+    simples[i] = jsoSimple;
+    assertEquals("a", simples[i++].a());
+
+    Object[] objects = simples;
+    try {
+      objects[i++] = new Object();
+      fail("ArrayStoreException expected");
+    } catch (ArrayStoreException expected) {
+    }
+  }
+
   public void testCallsToInnerTypes() {
     CallsMethodInInnerType a = (CallsMethodInInnerType) JavaScriptObject.createObject();
     InnerType i = (InnerType) JavaScriptObject.createObject();