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;