Support primitive array elements in annotations.

Annotations that include primitive array elements are stored internally
as arrays of boxed primitives.  The VM didn't know how to un-box them
when somebody requested the contents.

For bug 2370144.
diff --git a/tests/004-annotations/expected.txt b/tests/004-annotations/expected.txt
index 724b0eb..6caef04 100644
--- a/tests/004-annotations/expected.txt
+++ b/tests/004-annotations/expected.txt
@@ -1,4 +1,6 @@
 TestAnnotations...
+java.lang.String android.test.anno.TestAnnotations.thing1: @android.test.anno.AnnoArrayField(bb=[], cc=[a, b], dd=[0.987654321], ff=[3.1415927], ii=[], jj=[], ss=[], str=[], zz=[])
+java.lang.String android.test.anno.TestAnnotations.thing2: @android.test.anno.AnnoArrayField(bb=[-1, 0, 1], cc=[Q], dd=[0.3, 0.6, 0.9], ff=[1.1, 1.2, 1.3], ii=[1, 2, 3, 4], jj=[-5, 0, 5], ss=[12, 13, 14, 15, 16, 17], str=[hickory, dickory, dock], zz=[true, false, true])
 mapping is class [Landroid.test.anno.IntToString;
   0='@android.test.anno.IntToString(from=0, to=NORMAL_FOCUS)'
   1='@android.test.anno.IntToString(from=2, to=WEAK_FOCUS)'
@@ -85,7 +87,7 @@
   annotations on FIELD int android.test.anno.FullyNoted.mBar:
     @android.test.anno.AnnoFancyField(nombre=fubar)
       interface android.test.anno.AnnoFancyField
-    aff: @android.test.anno.AnnoFancyField(nombre=fubar) / class $Proxy16
+    aff: @android.test.anno.AnnoFancyField(nombre=fubar) / class $Proxy17
     --> nombre is 'fubar'
 
 SimplyNoted.get(AnnoSimpleType) = @android.test.anno.AnnoSimpleType()
diff --git a/tests/004-annotations/src/android/test/anno/AnnoArrayField.java b/tests/004-annotations/src/android/test/anno/AnnoArrayField.java
new file mode 100644
index 0000000..d929aac
--- /dev/null
+++ b/tests/004-annotations/src/android/test/anno/AnnoArrayField.java
@@ -0,0 +1,20 @@
+package android.test.anno;
+
+import java.lang.annotation.*;
+
+@Documented
+@Inherited
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AnnoArrayField {
+    boolean[] zz() default {};
+    byte[] bb() default {};
+    char[] cc() default {'a', 'b'};
+    short[] ss() default {};
+    int[] ii() default {};
+    float[] ff() default {3.141592654f};
+    long[] jj() default {};
+    double[] dd() default {0.987654321};
+    String[] str() default {};
+}
+
diff --git a/tests/004-annotations/src/android/test/anno/TestAnnotations.java b/tests/004-annotations/src/android/test/anno/TestAnnotations.java
index d1150f4..d7b0b14 100644
--- a/tests/004-annotations/src/android/test/anno/TestAnnotations.java
+++ b/tests/004-annotations/src/android/test/anno/TestAnnotations.java
@@ -81,6 +81,41 @@
         return 2;
     }
 
+
+    @AnnoArrayField
+    String thing1;
+
+    @AnnoArrayField(
+            zz = {true,false,true},
+            bb = {-1,0,1},
+            cc = {'Q'},
+            ss = {12,13,14,15,16,17},
+            ii = {1,2,3,4},
+            ff = {1.1f,1.2f,1.3f},
+            jj = {-5,0,5},
+            dd = {0.3,0.6,0.9},
+            str = {"hickory","dickory","dock"}
+            )
+    String thing2;
+
+    public static void testArrays() {
+        TestAnnotations ta = new TestAnnotations();
+        Field field;
+        Annotation[] annotations;
+
+        try {
+            field = TestAnnotations.class.getDeclaredField("thing1");
+            annotations = field.getAnnotations();
+            System.out.println(field + ": " + annotations[0].toString());
+
+            field = TestAnnotations.class.getDeclaredField("thing2");
+            annotations = field.getAnnotations();
+            System.out.println(field + ": " + annotations[0].toString());
+        } catch (NoSuchFieldException nsfe) {
+            throw new RuntimeException(nsfe);
+        }
+    }
+
     public static void testArrayProblem() {
         Method meth;
         ExportedProperty property;
@@ -105,6 +140,7 @@
     public static void main(String[] args) {
         System.out.println("TestAnnotations...");
 
+        testArrays();
         testArrayProblem();
         //System.exit(0);
 
diff --git a/vm/oo/Array.c b/vm/oo/Array.c
index 60da683..4af03a9 100644
--- a/vm/oo/Array.c
+++ b/vm/oo/Array.c
@@ -689,6 +689,82 @@
 }
 
 /*
+ * Copy the entire contents of an array of boxed primitives into an
+ * array of primitives.  The boxed value must fit in the primitive (i.e.
+ * narrowing conversions are not allowed).
+ */
+bool dvmUnboxObjectArray(ArrayObject* dstArray, const ArrayObject* srcArray,
+    ClassObject* dstElemClass)
+{
+    Object** src = (Object**)srcArray->contents;
+    void* dst = (void*)dstArray->contents;
+    u4 count = dstArray->length;
+    PrimitiveType typeIndex = dstElemClass->primitiveType;
+
+    assert(typeIndex != PRIM_NOT);
+    assert(srcArray->length == dstArray->length);
+
+    while (count--) {
+        JValue result;
+
+        /*
+         * This will perform widening conversions as appropriate.  It
+         * might make sense to be more restrictive and require that the
+         * primitive type exactly matches the box class, but it's not
+         * necessary for correctness.
+         */
+        if (!dvmUnwrapPrimitive(*src, dstElemClass, &result)) {
+            LOGW("dvmCopyObjectArray: can't store %s in %s\n",
+                (*src)->clazz->descriptor, dstElemClass->descriptor);
+            return false;
+        }
+
+        /* would be faster with 4 loops, but speed not crucial here */
+        switch (typeIndex) {
+        case PRIM_BOOLEAN:
+        case PRIM_BYTE:
+            {
+                u1* tmp = dst;
+                *tmp++ = result.b;
+                dst = tmp;
+            }
+            break;
+        case PRIM_CHAR:
+        case PRIM_SHORT:
+            {
+                u2* tmp = dst;
+                *tmp++ = result.s;
+                dst = tmp;
+            }
+            break;
+        case PRIM_FLOAT:
+        case PRIM_INT:
+            {
+                u4* tmp = dst;
+                *tmp++ = result.i;
+                dst = tmp;
+            }
+            break;
+        case PRIM_DOUBLE:
+        case PRIM_LONG:
+            {
+                u8* tmp = dst;
+                *tmp++ = result.j;
+                dst = tmp;
+            }
+            break;
+        default:
+            /* should not be possible to get here */
+            dvmAbort();
+        }
+
+        src++;
+    }
+
+    return true;
+}
+
+/*
  * Add all primitive classes to the root set of objects.
 TODO: do these belong to the root class loader?
  */
diff --git a/vm/oo/Array.h b/vm/oo/Array.h
index 868e48a..018f3d7 100644
--- a/vm/oo/Array.h
+++ b/vm/oo/Array.h
@@ -132,4 +132,12 @@
 bool dvmCopyObjectArray(ArrayObject* dstArray, const ArrayObject* srcArray,
     ClassObject* dstElemClass);
 
+/*
+ * Copy the entire contents of an array of boxed primitives into an
+ * array of primitives.  The boxed value must fit in the primitive (i.e.
+ * narrowing conversions are not allowed).
+ */
+bool dvmUnboxObjectArray(ArrayObject* dstArray, const ArrayObject* srcArray,
+    ClassObject* dstElemClass);
+
 #endif /*_DALVIK_OO_ARRAY*/
diff --git a/vm/reflect/Annotation.c b/vm/reflect/Annotation.c
index 109c7fb..fb4b83f 100644
--- a/vm/reflect/Annotation.c
+++ b/vm/reflect/Annotation.c
@@ -617,10 +617,8 @@
  *
  * For an array annotation, the type of the extracted object will always
  * be java.lang.Object[], but we want it to match the type that the
- * annotation member is expected to return.  In theory we can just stomp
- * the object's class to have the correct type, but this strikes me as a
- * risky proposition (at the very least we would need to call instanceof()
- * on every element).
+ * annotation member is expected to return.  In some cases this may
+ * involve un-boxing primitive values.
  *
  * We allocate a second array with the correct type, then copy the data
  * over.  This releases the tracked allocation on "valueObj" and returns
@@ -642,18 +640,26 @@
     ClassObject* dstElemClass;
 
     /*
-     * Strip off one '[' to get element class.  Note this is not the
-     * same as clazz->elementClass.
+     * We always extract kDexAnnotationArray into Object[], so we expect to
+     * find that here.  This means we can skip the FindClass on
+     * (valueObj->clazz->descriptor+1, valueObj->clazz->classLoader).
      */
-    srcElemClass = dvmFindClass(valueObj->clazz->descriptor+1,
-        valueObj->clazz->classLoader);
-    dstElemClass = dvmFindClass(methodReturn->descriptor+1,
-        methodReturn->classLoader);
-    if (srcElemClass->primitiveType != PRIM_NOT ||
-        dstElemClass->primitiveType != PRIM_NOT)
-    {
-        LOGE("ERROR: array of primitives not expected here\n");
-        dvmAbort();
+    if (strcmp(valueObj->clazz->descriptor, "[Ljava/lang/Object;") != 0) {
+        LOGE("Unexpected src type class (%s)\n", valueObj->clazz->descriptor);
+        return NULL;
+    }
+    srcElemClass = gDvm.classJavaLangObject;
+
+    /*
+     * Skip past the '[' to get element class name.  Note this is not always
+     * the same as methodReturn->elementClass.
+     */
+    char firstChar = methodReturn->descriptor[1];
+    if (firstChar == 'L' || firstChar == '[') {
+        dstElemClass = dvmFindClass(methodReturn->descriptor+1,
+            methodReturn->classLoader);
+    } else {
+        dstElemClass = dvmFindPrimitiveClass(firstChar);
     }
     LOGV("HEY: converting valueObj from [%s to [%s\n",
         srcElemClass->descriptor, dstElemClass->descriptor);
@@ -669,7 +675,13 @@
         goto bail;
     }
 
-    if (!dvmCopyObjectArray(newArray, srcArray, dstElemClass)) {
+    bool success;
+    if (dstElemClass->primitiveType == PRIM_NOT) {
+        success = dvmCopyObjectArray(newArray, srcArray, dstElemClass);
+    } else {
+        success = dvmUnboxObjectArray(newArray, srcArray, dstElemClass);
+    }
+    if (!success) {
         LOGE("Annotation array copy failed\n");
         dvmReleaseTrackedAlloc((Object*)newArray, self);
         newArray = NULL;