Fixes issue #2139; web mode was throwing an ArrayStoreException trying to store a JavaScriptObject into Object[].

Reviewer's comments:
- I wish there was a less tricky way, something with an overt check for Object instead of relying on 0 being special, but having something that works is a big improvement on the current state.  Anything should be storable into an Object, even a native JSO that has no typeId!

Found by: sanjiv.jivan
Review by: spoon (postmortem)


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1961 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java
index 37d1be2..c340f98 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java
@@ -48,10 +48,25 @@
 
 /**
  * Replace cast and instanceof operations with calls to the Cast class. Depends
- * on {@link com.google.gwt.dev.jjs.impl.CatchBlockNormalizer},
- * {@link com.google.gwt.dev.jjs.impl.CompoundAssignmentNormalizer}, and
- * {@link com.google.gwt.dev.jjs.impl.JavaScriptObjectCaster} having already
- * run.
+ * on {@link CatchBlockNormalizer}, {@link CompoundAssignmentNormalizer}, and
+ * {@link JsoDevirtualizer} having already run.
+ * 
+ * <p>
+ * Object and String always get a typeId of 1 and 2, respectively. 0 is reserved
+ * as the typeId for any classes that can never be instance type of a successful
+ * dynamic cast.
+ * </p>
+ * <p>
+ * Object and String always get a queryId of 0 and 1, respectively. The 0
+ * queryId always means "always succeeds". In practice, we never generate an
+ * explicit cast with a queryId of 0; it is only used for array store checking,
+ * where the 0 queryId means that anything can be stored into an Object[].
+ * </p>
+ * <p>
+ * JavaScriptObject and subclasses have no typeId at all. JavaScriptObject has a
+ * queryId of -1, which again is only used for array store checking, to ensure
+ * that a non-JSO is not stored into a JavaScriptObject[].
+ * </p>
  */
 public class CastNormalizer {
 
@@ -59,7 +74,7 @@
 
     Set<JClassType> alreadyRan = new HashSet<JClassType>();
     private Map<JReferenceType, Set<JReferenceType>> queriedTypes = new IdentityHashMap<JReferenceType, Set<JReferenceType>>();
-    private int nextQueryId = 1; // 0 is reserved
+    private int nextQueryId = 0;
     private final List<JArrayType> instantiatedArrayTypes = new ArrayList<JArrayType>();
     private List<JClassType> classes = new ArrayList<JClassType>();
     private List<JsonObject> jsonObjects = new ArrayList<JsonObject>();
@@ -72,6 +87,10 @@
         }
       }
 
+      // Reserve query id 0 for java.lang.Object (for array stores on JSOs).
+      recordCastInternal(program.getTypeJavaLangObject(),
+          program.getTypeJavaLangObject());
+
       // Reserve query id 1 for java.lang.String to facilitate the mashup case.
       // Multiple GWT modules need to modify String's prototype the same way.
       recordCastInternal(program.getTypeJavaLangString(),
@@ -227,12 +246,7 @@
         }
       }
 
-      // Object a typeId, to force String to have an id of 2.
-      if (yesSet == null && type != program.getTypeJavaLangObject()) {
-        return; // won't satisfy anything
-      }
-
-      // use an array to sort my yes set
+      // Use a sparse array to sort my yes set.
       JReferenceType[] yesArray = new JReferenceType[nextQueryId];
       if (yesSet != null) {
         for (JReferenceType yesType : yesSet) {
@@ -240,9 +254,10 @@
         }
       }
 
-      // create a sparse lookup object
+      // Create a sparse lookup object.
       JsonObject jsonObject = new JsonObject(program);
-      for (int i = 0; i < nextQueryId; ++i) {
+      // Start at 1; 0 is Object and always true.
+      for (int i = 1; i < nextQueryId; ++i) {
         if (yesArray[i] != null) {
           JIntLiteral labelExpr = program.getLiteralInt(i);
           JIntLiteral valueExpr = program.getLiteralInt(1);
@@ -251,6 +266,16 @@
         }
       }
 
+      /*
+       * Don't add an entry for empty answer sets, except for Object and String
+       * which require typeIds.
+       */
+      if (jsonObject.propInits.isEmpty()
+          && type != program.getTypeJavaLangObject()
+          && type != program.getTypeJavaLangString()) {
+        return;
+      }
+
       // add an entry for me
       classes.add(type);
       jsonObjects.add(jsonObject);
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 21bd74d..685b120 100644
--- a/user/test/com/google/gwt/dev/jjs/test/JsoTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/JsoTest.java
@@ -59,7 +59,7 @@
 
     protected Foo() {
     }
-    
+
     public final native String getFoo() /*-{
       return this.foo;
     }-*/;
@@ -262,6 +262,11 @@
       fail("Expected ArrayStoreException");
     } catch (ArrayStoreException expected) {
     }
+
+    objArray = new Object[1];
+    objArray[0] = makeJSO();
+    objArray[0] = makeFoo();
+    objArray[0] = makeBar();
   }
 
   public void testBasic() {