8073056: Repeating annotations throws java.security.AccessControlException with a SecurityManager
Reviewed-by: ahgross, darcy
--- 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();
+ }
+}