7183985: (ann) Class.getAnnotation() throws an ArrayStoreException when the annotation class not present
authorcushon
Thu, 14 Jun 2018 17:32:31 -0700
changeset 50591 9f4c08c444e8
parent 50590 5fa19bad622d
child 50592 dddf078c242f
7183985: (ann) Class.getAnnotation() throws an ArrayStoreException when the annotation class not present Reviewed-by: darcy, martin, vromero
src/java.base/share/classes/sun/reflect/annotation/AnnotationParser.java
test/jdk/java/lang/annotation/Missing/MissingArrayElement/EnumToCompileAgainst.java
test/jdk/java/lang/annotation/Missing/MissingArrayElement/EnumToRunAgainst.java
test/jdk/java/lang/annotation/Missing/MissingArrayElement/MissingAnnotation.java
test/jdk/java/lang/annotation/Missing/MissingArrayElement/MissingAnnotationArrayElementTest.java
test/jdk/java/lang/annotation/Missing/MissingArrayElement/MissingClass.java
test/jdk/java/lang/annotation/Missing/MissingArrayElement/MissingClass2.java
test/jdk/java/lang/annotation/Missing/MissingArrayElement/MissingClassArrayElementTest.java
test/jdk/java/lang/annotation/Missing/MissingArrayElement/MissingEnumArrayElementTest.java
--- a/src/java.base/share/classes/sun/reflect/annotation/AnnotationParser.java	Fri Jun 15 09:53:28 2018 -0700
+++ b/src/java.base/share/classes/sun/reflect/annotation/AnnotationParser.java	Thu Jun 14 17:32:31 2018 -0700
@@ -715,19 +715,23 @@
                                           ConstantPool constPool,
                                           Class<?> container) {
         Object[] result = new Class<?>[length];
-        boolean typeMismatch = false;
-        int tag = 0;
+        Object exceptionProxy = null;
 
         for (int i = 0; i < length; i++) {
-            tag = buf.get();
+            int tag = buf.get();
             if (tag == 'c') {
-                result[i] = parseClassValue(buf, constPool, container);
+                Object value = parseClassValue(buf, constPool, container);
+                if (value instanceof ExceptionProxy) {
+                    if (exceptionProxy == null) exceptionProxy = (ExceptionProxy) value;
+                } else {
+                    result[i] = value;
+                }
             } else {
                 skipMemberValue(tag, buf);
-                typeMismatch = true;
+                if (exceptionProxy == null) exceptionProxy = exceptionProxy(tag);
             }
         }
-        return typeMismatch ? exceptionProxy(tag) : result;
+        return (exceptionProxy != null) ? exceptionProxy : result;
     }
 
     private static Object parseEnumArray(int length, Class<? extends Enum<?>> enumType,
@@ -735,19 +739,23 @@
                                          ConstantPool constPool,
                                          Class<?> container) {
         Object[] result = (Object[]) Array.newInstance(enumType, length);
-        boolean typeMismatch = false;
-        int tag = 0;
+        Object exceptionProxy = null;
 
         for (int i = 0; i < length; i++) {
-            tag = buf.get();
+            int tag = buf.get();
             if (tag == 'e') {
-                result[i] = parseEnumValue(enumType, buf, constPool, container);
+                Object value = parseEnumValue(enumType, buf, constPool, container);
+                if (value instanceof ExceptionProxy) {
+                    if (exceptionProxy == null) exceptionProxy = (ExceptionProxy) value;
+                } else {
+                    result[i] = value;
+                }
             } else {
                 skipMemberValue(tag, buf);
-                typeMismatch = true;
+                if (exceptionProxy == null) exceptionProxy = exceptionProxy(tag);
             }
         }
-        return typeMismatch ? exceptionProxy(tag) : result;
+        return (exceptionProxy != null) ? exceptionProxy : result;
     }
 
     private static Object parseAnnotationArray(int length,
@@ -756,19 +764,23 @@
                                                ConstantPool constPool,
                                                Class<?> container) {
         Object[] result = (Object[]) Array.newInstance(annotationType, length);
-        boolean typeMismatch = false;
-        int tag = 0;
+        Object exceptionProxy = null;
 
         for (int i = 0; i < length; i++) {
-            tag = buf.get();
+            int tag = buf.get();
             if (tag == '@') {
-                result[i] = parseAnnotation(buf, constPool, container, true);
+                Object value = parseAnnotation(buf, constPool, container, true);
+                if (value instanceof ExceptionProxy) {
+                    if (exceptionProxy == null) exceptionProxy = (ExceptionProxy) value;
+                } else {
+                    result[i] = value;
+                }
             } else {
                 skipMemberValue(tag, buf);
-                typeMismatch = true;
+                if (exceptionProxy == null) exceptionProxy = exceptionProxy(tag);
             }
         }
-        return typeMismatch ? exceptionProxy(tag) : result;
+        return (exceptionProxy != null) ? exceptionProxy : result;
     }
 
     /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/annotation/Missing/MissingArrayElement/EnumToCompileAgainst.java	Thu Jun 14 17:32:31 2018 -0700
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2018, Google Inc. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * The enum that will be seen at compile-time.
+ *
+ * <p>The filename deliberately does not match, since we need to declare two versions of the enum
+ * for compile-time and runtime.
+ */
+enum Enum {
+    ONE,
+    TWO,
+    THREE
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/annotation/Missing/MissingArrayElement/EnumToRunAgainst.java	Thu Jun 14 17:32:31 2018 -0700
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2018, Google Inc. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * The enum that will be seen at runtime.
+ *
+ * <p>The filename deliberately does not match, since we need to declare two versions of the enum
+ * for compile-time and runtime.
+ */
+enum Enum {
+    ONE
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/annotation/Missing/MissingArrayElement/MissingAnnotation.java	Thu Jun 14 17:32:31 2018 -0700
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018, Google Inc. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+
+/** An annotation that will be missing at runtime. */
+@Retention(RUNTIME)
+public @interface MissingAnnotation {}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/annotation/Missing/MissingArrayElement/MissingAnnotationArrayElementTest.java	Thu Jun 14 17:32:31 2018 -0700
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2018, Google Inc. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/*
+ * @test
+ * @bug 7183985
+ * @summary getAnnotation() should throw NoClassDefFoundError when an annotation class is not
+ *          present at runtime
+ * @compile MissingAnnotationArrayElementTest.java MissingAnnotation.java
+ * @clean MissingAnnotation
+ * @run main MissingAnnotationArrayElementTest
+ */
+public class MissingAnnotationArrayElementTest {
+
+    @Retention(RUNTIME)
+    @interface AnnotationAnnotation {
+        MissingAnnotation[] value();
+    }
+
+    @AnnotationAnnotation({@MissingAnnotation, @MissingAnnotation})
+    static class Test {
+        void f(@AnnotationAnnotation({@MissingAnnotation, @MissingAnnotation}) int x) {}
+
+        Test(@AnnotationAnnotation({@MissingAnnotation, @MissingAnnotation}) int x) {}
+    }
+
+    public static void main(String[] args) throws Exception {
+        // MissingAnnotation will be absent from the runtime classpath, causing a
+        // NoClassDefFoundError when AnnotationAnnotation is read (since the type of its value array
+        // references cannot be completed).
+        assertThrowsNoClassDefFoundError(
+                () -> Test.class.getAnnotation(AnnotationAnnotation.class));
+        Method method = Test.class.getDeclaredMethod("f", int.class);
+        assertThrowsNoClassDefFoundError(method::getParameterAnnotations);
+        Constructor constructor = Test.class.getDeclaredConstructor(int.class);
+        assertThrowsNoClassDefFoundError(constructor::getParameterAnnotations);
+    }
+
+    interface ThrowingRunnable {
+        void run() throws Exception;
+    }
+
+    static void assertThrowsNoClassDefFoundError(ThrowingRunnable throwingRunnable)
+            throws Exception {
+        try {
+            throwingRunnable.run();
+            throw new AssertionError("expected exception");
+        } catch (NoClassDefFoundError expected) {
+            if (!expected.getMessage().contains("MissingAnnotation")) {
+                throw expected;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/annotation/Missing/MissingArrayElement/MissingClass.java	Thu Jun 14 17:32:31 2018 -0700
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2018, Google Inc. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/** A class that will be missing at runtime. */
+public class MissingClass {}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/annotation/Missing/MissingArrayElement/MissingClass2.java	Thu Jun 14 17:32:31 2018 -0700
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2018, Google Inc. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/** A class that will be missing at runtime. */
+public class MissingClass2 {}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/annotation/Missing/MissingArrayElement/MissingClassArrayElementTest.java	Thu Jun 14 17:32:31 2018 -0700
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2018, Google Inc. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.util.Arrays;
+
+/*
+ * @test
+ * @bug 7183985
+ * @summary getAnnotation() throws an ArrayStoreException when the annotation class not present
+ * @compile MissingClassArrayElementTest.java MissingClass.java MissingClass2.java
+ * @clean MissingClass MissingClass2
+ * @run main MissingClassArrayElementTest
+ */
+public class MissingClassArrayElementTest {
+
+    @Retention(RUNTIME)
+    @interface AnnotationAnnotation {
+        ClassArrayAnnotation[] value();
+    }
+
+    @Retention(RUNTIME)
+    @interface ClassArrayAnnotation {
+        Class<?>[] value();
+    }
+
+    @AnnotationAnnotation({
+        @ClassArrayAnnotation({MissingClass.class}),
+        @ClassArrayAnnotation({MissingClass.class, String.class}),
+        @ClassArrayAnnotation({String.class, MissingClass.class}),
+        @ClassArrayAnnotation({MissingClass.class, MissingClass2.class}),
+        @ClassArrayAnnotation({String.class})
+    })
+    static class Test {
+        void f(
+                @AnnotationAnnotation({
+                            @ClassArrayAnnotation({MissingClass.class, MissingClass.class}),
+                            @ClassArrayAnnotation({Float.class})
+                        })
+                        int x,
+                @AnnotationAnnotation({@ClassArrayAnnotation({Double.class})}) int y) {}
+
+        Test(
+                @AnnotationAnnotation({
+                            @ClassArrayAnnotation({MissingClass.class, MissingClass.class}),
+                            @ClassArrayAnnotation({Short.class}),
+                        })
+                        int x,
+                @AnnotationAnnotation({@ClassArrayAnnotation({Character.class})}) int y) {}
+    }
+
+    public static void main(String[] args) throws Exception {
+        classAnnotationTest();
+        methodParameterAnnotationsTest();
+        constructorParameterAnnotationsTest();
+    }
+
+    static void classAnnotationTest() throws Exception {
+        ClassArrayAnnotation[] outer = Test.class.getAnnotation(AnnotationAnnotation.class).value();
+        assertMissing(outer[0]);
+        assertMissing(outer[1]);
+        assertMissing(outer[2]);
+        assertMissing(outer[3]);
+        assertArrayEquals(outer[4].value(), new Class<?>[] {String.class});
+    }
+
+    static void methodParameterAnnotationsTest() throws Exception {
+        AnnotationAnnotation[] methodParameterAnnotations =
+                Arrays.stream(
+                                Test.class
+                                        .getDeclaredMethod("f", int.class, int.class)
+                                        .getParameterAnnotations())
+                        .map(x -> ((AnnotationAnnotation) x[0]))
+                        .toArray(AnnotationAnnotation[]::new);
+        // The first parameter's annotation contains some well-formed values, and the second
+        // parameter's
+        // annotation is well-formed
+        assertArrayEquals(
+                methodParameterAnnotations[0].value()[1].value(), new Class<?>[] {Float.class});
+        assertArrayEquals(
+                methodParameterAnnotations[1].value()[0].value(), new Class<?>[] {Double.class});
+        // The first parameter's annotation contains a missing value
+        assertMissing(methodParameterAnnotations[0].value()[0]);
+    }
+
+    static void constructorParameterAnnotationsTest() throws Exception {
+        AnnotationAnnotation[] constructorParameterAnnotations =
+                Arrays.stream(
+                                Test.class
+                                        .getDeclaredConstructor(int.class, int.class)
+                                        .getParameterAnnotations())
+                        .map(x -> ((AnnotationAnnotation) x[0]))
+                        .toArray(AnnotationAnnotation[]::new);
+        // The first parameter's annotation contains some well-formed values, and the second
+        // parameter's
+        // annotation is well-formed
+        assertArrayEquals(
+                constructorParameterAnnotations[0].value()[1].value(),
+                new Class<?>[] {Short.class});
+        assertArrayEquals(
+                constructorParameterAnnotations[1].value()[0].value(),
+                new Class<?>[] {Character.class});
+        // The first parameter's annotation contains a missing value
+        assertMissing(constructorParameterAnnotations[0].value()[0]);
+    }
+
+    static void assertArrayEquals(Object[] actual, Object[] expected) {
+        if (!Arrays.equals(actual, expected)) {
+            throw new AssertionError(
+                    "expected: " + Arrays.toString(expected) + ", was: " + Arrays.toString(actual));
+        }
+    }
+
+    static void assertMissing(ClassArrayAnnotation missing) {
+        try {
+            missing.value();
+            throw new AssertionError("expected exception");
+        } catch (TypeNotPresentException expected) {
+            if (!expected.typeName().equals("MissingClass")) {
+                throw new AssertionError(
+                        "expected TypeNotPresentException: MissingClass", expected);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/annotation/Missing/MissingArrayElement/MissingEnumArrayElementTest.java	Thu Jun 14 17:32:31 2018 -0700
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2018, Google Inc. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.util.Arrays;
+
+/*
+ * @test
+ * @bug 7183985
+ * @summary getAnnotation() throws an ArrayStoreException when the annotation class not present
+ * @compile MissingEnumArrayElementTest.java EnumToCompileAgainst.java
+ * @clean Enum
+ * @compile EnumToRunAgainst.java
+ * @run main MissingEnumArrayElementTest
+ */
+public class MissingEnumArrayElementTest {
+
+    @Retention(RUNTIME)
+    @interface AnnotationAnnotation {
+        EnumArrayAnnotation[] value();
+    }
+
+    @Retention(RUNTIME)
+    @interface EnumArrayAnnotation {
+        Enum[] value();
+    }
+
+    @AnnotationAnnotation({
+        @EnumArrayAnnotation({Enum.TWO}),
+        @EnumArrayAnnotation({Enum.ONE, Enum.TWO}),
+        @EnumArrayAnnotation({Enum.TWO, Enum.ONE}),
+        @EnumArrayAnnotation({Enum.TWO, Enum.THREE}),
+        @EnumArrayAnnotation({Enum.ONE}),
+    })
+    static class Test {
+        void f(
+                @AnnotationAnnotation({
+                            @EnumArrayAnnotation({Enum.TWO, Enum.ONE}),
+                            @EnumArrayAnnotation({Enum.ONE})
+                        })
+                        int x,
+                @AnnotationAnnotation({@EnumArrayAnnotation({Enum.ONE})}) int y) {}
+
+        Test(
+                @AnnotationAnnotation({
+                            @EnumArrayAnnotation({Enum.TWO, Enum.ONE}),
+                            @EnumArrayAnnotation({Enum.ONE})
+                        })
+                        int x,
+                @AnnotationAnnotation({@EnumArrayAnnotation({Enum.ONE})}) int y) {}
+    }
+
+    public static void main(String[] args) throws Exception {
+        classAnnotationTest();
+        methodParameterAnnotationsTest();
+        constructorParameterAnnotationsTest();
+    }
+
+    static void classAnnotationTest() throws Exception {
+        EnumArrayAnnotation[] outer = Test.class.getAnnotation(AnnotationAnnotation.class).value();
+        assertMissing(outer[0]);
+        assertMissing(outer[1]);
+        assertMissing(outer[2]);
+        assertMissing(outer[3]);
+        assertArrayEquals(outer[4].value(), new Enum[] {Enum.ONE});
+    }
+
+    static void methodParameterAnnotationsTest() throws Exception {
+        AnnotationAnnotation[] methodParameterAnnotations =
+                Arrays.stream(
+                                Test.class
+                                        .getDeclaredMethod("f", int.class, int.class)
+                                        .getParameterAnnotations())
+                        .map(x -> ((AnnotationAnnotation) x[0]))
+                        .toArray(AnnotationAnnotation[]::new);
+        // The first parameter's annotation contains some well-formed values, and the second
+        // parameter's
+        // annotation is well-formed
+        assertArrayEquals(methodParameterAnnotations[0].value()[1].value(), new Enum[] {Enum.ONE});
+        assertArrayEquals(methodParameterAnnotations[1].value()[0].value(), new Enum[] {Enum.ONE});
+        // The first parameter's annotation contains a missing value
+        assertMissing(methodParameterAnnotations[0].value()[0]);
+    }
+
+    static void constructorParameterAnnotationsTest() throws Exception {
+        AnnotationAnnotation[] constructorParameterAnnotations =
+                Arrays.stream(
+                                Test.class
+                                        .getDeclaredConstructor(int.class, int.class)
+                                        .getParameterAnnotations())
+                        .map(x -> ((AnnotationAnnotation) x[0]))
+                        .toArray(AnnotationAnnotation[]::new);
+        // The first parameter's annotation contains some well-formed values, and the second
+        // parameter's
+        // annotation is well-formed
+        assertArrayEquals(constructorParameterAnnotations[0].value()[1].value(), new Enum[] {Enum.ONE});
+        assertArrayEquals(constructorParameterAnnotations[1].value()[0].value(), new Enum[] {Enum.ONE});
+        // The first parameter's annotation contains a missing value
+        assertMissing(constructorParameterAnnotations[0].value()[0]);
+    }
+
+    static void assertArrayEquals(Object[] actual, Object[] expected) {
+        if (!Arrays.equals(actual, expected)) {
+            throw new AssertionError(
+                    "expected: " + Arrays.toString(expected) + ", was: " + Arrays.toString(actual));
+        }
+    }
+
+    static void assertMissing(EnumArrayAnnotation annotation) throws Exception {
+        try {
+            annotation.value();
+            throw new AssertionError("expected exception");
+        } catch (EnumConstantNotPresentException expected) {
+            if (!expected.getMessage().equals("Enum.TWO")) {
+                throw new AssertionError(
+                        "expected EnumConstantNotPresentException for Enum.TWO", expected);
+            }
+        }
+    }
+}