8073056: Repeating annotations throws java.security.AccessControlException with a SecurityManager
authorjfranck
Mon, 24 Aug 2015 11:00:12 +0200
changeset 32268 9fe7ee60d49d
parent 32267 4e96a9ee01b1
child 32269 cb52640fd0e8
8073056: Repeating annotations throws java.security.AccessControlException with a SecurityManager Reviewed-by: ahgross, darcy
jdk/src/java.base/share/classes/java/lang/reflect/Method.java
jdk/src/java.base/share/classes/java/lang/reflect/ReflectAccess.java
jdk/src/java.base/share/classes/sun/reflect/LangReflectAccess.java
jdk/src/java.base/share/classes/sun/reflect/ReflectionFactory.java
jdk/src/java.base/share/classes/sun/reflect/annotation/AnnotationSupport.java
jdk/test/java/lang/annotation/repeatingAnnotations/CustomRepeatingWithSecurityManager.java
jdk/test/java/lang/annotation/repeatingAnnotations/RepeatingWithSecurityManager.java
--- a/jdk/src/java.base/share/classes/java/lang/reflect/Method.java	Wed Jul 08 23:26:48 2015 -0700
+++ b/jdk/src/java.base/share/classes/java/lang/reflect/Method.java	Mon Aug 24 11:00:12 2015 +0200
@@ -161,6 +161,21 @@
     }
 
     /**
+     * Make a copy of a leaf method.
+     */
+    Method leafCopy() {
+        if (this.root == null)
+            throw new IllegalArgumentException("Can only leafCopy a non-root Method");
+
+        Method res = new Method(clazz, name, parameterTypes, returnType,
+                exceptionTypes, modifiers, slot, signature,
+                annotations, parameterAnnotations, annotationDefault);
+        res.root = root;
+        res.methodAccessor = methodAccessor;
+        return res;
+    }
+
+    /**
      * Used by Excecutable for annotation sharing.
      */
     @Override
--- a/jdk/src/java.base/share/classes/java/lang/reflect/ReflectAccess.java	Wed Jul 08 23:26:48 2015 -0700
+++ b/jdk/src/java.base/share/classes/java/lang/reflect/ReflectAccess.java	Mon Aug 24 11:00:12 2015 +0200
@@ -139,6 +139,9 @@
     public Method      copyMethod(Method arg) {
         return arg.copy();
     }
+    public Method      leafCopyMethod(Method arg) {
+        return arg.leafCopy();
+    }
 
     public Field       copyField(Field arg) {
         return arg.copy();
--- a/jdk/src/java.base/share/classes/sun/reflect/LangReflectAccess.java	Wed Jul 08 23:26:48 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/reflect/LangReflectAccess.java	Mon Aug 24 11:00:12 2015 +0200
@@ -104,6 +104,9 @@
     /** Makes a "child" copy of a Method */
     public Method      copyMethod(Method arg);
 
+    /** Makes a copy of this non-root a Method */
+    public Method      leafCopyMethod(Method arg);
+
     /** Makes a "child" copy of a Field */
     public Field       copyField(Field arg);
 
--- a/jdk/src/java.base/share/classes/sun/reflect/ReflectionFactory.java	Wed Jul 08 23:26:48 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/reflect/ReflectionFactory.java	Mon Aug 24 11:00:12 2015 +0200
@@ -302,6 +302,14 @@
         return langReflectAccess().copyMethod(arg);
     }
 
+    /** Makes a copy of the passed method. The returned method is NOT
+     * a "child" but a "sibling" of the Method in arg. Should only be
+     * used on non-root methods. */
+    public Method leafCopyMethod(Method arg) {
+        return langReflectAccess().leafCopyMethod(arg);
+    }
+
+
     /** Makes a copy of the passed field. The returned field is a
         "child" of the passed one; see the comments in Field.java for
         details. */
--- a/jdk/src/java.base/share/classes/sun/reflect/annotation/AnnotationSupport.java	Wed Jul 08 23:26:48 2015 -0700
+++ b/jdk/src/java.base/share/classes/sun/reflect/annotation/AnnotationSupport.java	Mon Aug 24 11:00:12 2015 +0200
@@ -27,14 +27,17 @@
 
 import java.lang.annotation.*;
 import java.lang.reflect.*;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
 import sun.misc.JavaLangAccess;
+import sun.reflect.LangReflectAccess;
+import sun.reflect.ReflectionFactory;
 
 public final class AnnotationSupport {
     private static final JavaLangAccess LANG_ACCESS = sun.misc.SharedSecrets.getJavaLangAccess();
@@ -62,7 +65,7 @@
     public static <A extends Annotation> A[] getDirectlyAndIndirectlyPresent(
             Map<Class<? extends Annotation>, Annotation> annotations,
             Class<A> annoClass) {
-        List<A> result = new ArrayList<A>();
+        List<A> result = new ArrayList<>();
 
         @SuppressWarnings("unchecked")
         A direct = (A) annotations.get(annoClass);
@@ -188,27 +191,68 @@
             AnnotationType annoType = AnnotationType.getInstance(containerClass);
             if (annoType == null)
                 throw invalidContainerException(container, null);
-
             Method m = annoType.members().get("value");
             if (m == null)
                 throw invalidContainerException(container, null);
 
-            m.setAccessible(true);
+            if (Proxy.isProxyClass(container.getClass())) {
+                // Invoke by invocation handler
+                InvocationHandler handler = Proxy.getInvocationHandler(container);
+
+                try {
+                    // This will erase to (Annotation[]) but we do a runtime cast on the
+                    // return-value in the method that call this method.
+                    @SuppressWarnings("unchecked")
+                    A[] values = (A[]) handler.invoke(container, m, null);
+                    return values;
+                } catch (Throwable t) { // from InvocationHandler::invoke
+                    throw invalidContainerException(container, t);
+                }
+            } else {
+                // In theory there might be instances of Annotations that are not
+                // implemented using Proxies. Try to invoke the "value" element with
+                // reflection.
+
+                // Declaring class should be an annotation type
+                Class<?> iface = m.getDeclaringClass();
+                if (!iface.isAnnotation())
+                    throw new UnsupportedOperationException("Unsupported container annotation type.");
+                // Method must be public
+                if (!Modifier.isPublic(m.getModifiers()))
+                    throw new UnsupportedOperationException("Unsupported value member.");
 
-            // This will erase to (Annotation[]) but we do a runtime cast on the
-            // return-value in the method that call this method.
-            @SuppressWarnings("unchecked")
-            A[] values = (A[]) m.invoke(container);
+                // Interface might not be public though
+                final Method toInvoke;
+                if (!Modifier.isPublic(iface.getModifiers())) {
+                    if (System.getSecurityManager() != null) {
+                        toInvoke = AccessController.doPrivileged(new PrivilegedAction<Method>() {
+                            @Override
+                            public Method run() {
+                                Method res = ReflectionFactory.getReflectionFactory().leafCopyMethod(m);
+                                res.setAccessible(true);
+                                return res;
+                            }
+                        });
+                    } else {
+                        toInvoke = ReflectionFactory.getReflectionFactory().leafCopyMethod(m);
+                        toInvoke.setAccessible(true);
+                    }
+                } else {
+                    toInvoke = m;
+                }
 
-            return values;
+                // This will erase to (Annotation[]) but we do a runtime cast on the
+                // return-value in the method that call this method.
+                @SuppressWarnings("unchecked")
+                A[] values = (A[]) toInvoke.invoke(container);
 
+                return values;
+            }
         } catch (IllegalAccessException    | // couldn't loosen security
                  IllegalArgumentException  | // parameters doesn't match
                  InvocationTargetException | // the value method threw an exception
                  ClassCastException e) {
-
             throw invalidContainerException(container, e);
-
         }
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/annotation/repeatingAnnotations/CustomRepeatingWithSecurityManager.java	Mon Aug 24 11:00:12 2015 +0200
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. 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.
+ */
+
+/*
+ * @test
+ * @bug     8073056
+ * @summary Repeating annotations throws java.security.AccessControlException with a SecurityManager
+ *
+ * @library /lib/testlibrary
+ * @build jdk.testlibrary.Asserts
+ * @run main CustomRepeatingWithSecurityManager
+ * @run main/othervm CustomRepeatingWithSecurityManager "withSM"
+ */
+
+import java.lang.annotation.*;
+import java.lang.reflect.*;
+
+import jdk.testlibrary.Asserts;
+
+public class CustomRepeatingWithSecurityManager {
+    public static void main(String[] args) throws Exception {
+        if (args.length == 1) {
+            SecurityManager sm = new SecurityManager();
+            System.setSecurityManager(sm);
+        }
+
+        Asserts.assertTrue(new CustomAnnotations().getAnnotationsByType(MyAnnotation.class).length == 2,
+                "Array should contain 2 annotations");
+        Asserts.assertEquals(new CustomAnnotations().getAnnotationsByType(MyAnnotation.class)[1].name(),
+                "Bar", "Should be 'Bar'");
+    }
+
+    static class CustomAnnotations implements AnnotatedElement {
+        @Override
+        public Annotation[] getDeclaredAnnotations() {
+            Annotation[] res = new Annotation[1];
+            res[0] = new MyAnnotationsImpl();
+            return res;
+        }
+
+        @Override
+        public Annotation[] getAnnotations() {
+            return getDeclaredAnnotations();
+        }
+
+        @Override
+        public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
+            return null;
+        }
+    }
+
+    static class MyAnnotationsImpl implements MyAnnotations {
+        public MyAnnotation[] value() {
+            MyAnnotation[] res = new MyAnnotation[2];
+            res[0] = new MyAnnotationImpl("Foo");
+            res[1] = new MyAnnotationImpl("Bar");
+            return res;
+        }
+
+        @Override
+        public Class<? extends Annotation> annotationType() {
+            return MyAnnotations.class;
+        }
+    }
+
+    static class MyAnnotationImpl implements MyAnnotation {
+        private String val;
+        MyAnnotationImpl(String val) {
+            this.val = val;
+        }
+
+        public String name() { return val; }
+
+        @Override
+        public Class<? extends Annotation> annotationType() {
+            return MyAnnotations.class;
+        }
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @interface MyAnnotations {
+        MyAnnotation[] value();
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Repeatable(MyAnnotations.class)
+    @interface MyAnnotation {
+        String name();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/annotation/repeatingAnnotations/RepeatingWithSecurityManager.java	Mon Aug 24 11:00:12 2015 +0200
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. 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.
+ */
+
+/*
+ * @test
+ * @bug     8073056
+ * @summary Repeating annotations throws java.security.AccessControlException with a SecurityManager
+ *
+ * @library /lib/testlibrary
+ * @build jdk.testlibrary.Asserts
+ * @run main RepeatingWithSecurityManager
+ * @run main/othervm RepeatingWithSecurityManager "withSM"
+ */
+
+import java.lang.annotation.*;
+import java.util.*;
+
+import jdk.testlibrary.Asserts;
+
+public class RepeatingWithSecurityManager {
+    public static void main(String[] args) throws Exception {
+        if (args.length == 1) {
+            SecurityManager sm = new SecurityManager();
+            System.setSecurityManager(sm);
+        }
+
+        Asserts.assertTrue(TwoAnnotations.class.getAnnotationsByType(MyAnnotation.class).length == 2,
+                "Array should contain 2 annotations: " +
+                Arrays.toString(TwoAnnotations.class.getAnnotationsByType(MyAnnotation.class)));
+    }
+
+    @MyAnnotation(name = "foo")
+    @MyAnnotation(name = "bar")
+    private static class TwoAnnotations {
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @interface MyAnnotations {
+        MyAnnotation[] value();
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Repeatable(MyAnnotations.class)
+    @interface MyAnnotation {
+        String name();
+    }
+}