--- old/src/share/classes/java/lang/Class.java 2013-06-24 20:09:28.116593070 +0200 +++ new/src/share/classes/java/lang/Class.java 2013-06-24 20:09:28.011594960 +0200 @@ -2520,7 +2520,7 @@ } // Annotations handling - private native byte[] getRawAnnotations(); + native byte[] getRawAnnotations(); // Since 1.8 native byte[] getRawTypeAnnotations(); static byte[] getExecutableTypeAnnotationBytes(Executable ex) { --- old/src/share/classes/java/lang/System.java 2013-06-24 20:09:28.453587004 +0200 +++ new/src/share/classes/java/lang/System.java 2013-06-24 20:09:28.351588840 +0200 @@ -1224,6 +1224,9 @@ public AnnotationType getAnnotationType(Class klass) { return klass.getAnnotationType(); } + public byte[] getRawClassAnnotations(Class klass) { + return klass.getRawAnnotations(); + } public byte[] getRawClassTypeAnnotations(Class klass) { return klass.getRawTypeAnnotations(); } --- old/src/share/classes/sun/misc/JavaLangAccess.java 2013-06-24 20:09:28.759581496 +0200 +++ new/src/share/classes/sun/misc/JavaLangAccess.java 2013-06-24 20:09:28.650583458 +0200 @@ -49,6 +49,12 @@ /** * Get the array of bytes that is the class-file representation + * of this Class' annotations. + */ + byte[] getRawClassAnnotations(Class klass); + + /** + * Get the array of bytes that is the class-file representation * of this Class' type annotations. */ byte[] getRawClassTypeAnnotations(Class klass); --- old/src/share/classes/sun/reflect/annotation/AnnotationParser.java 2013-06-24 20:09:29.055576167 +0200 +++ new/src/share/classes/sun/reflect/annotation/AnnotationParser.java 2013-06-24 20:09:28.946578130 +0200 @@ -69,7 +69,35 @@ return Collections.emptyMap(); try { - return parseAnnotations2(rawAnnotations, constPool, container); + return parseAnnotations2(rawAnnotations, constPool, container, null); + } catch(BufferUnderflowException e) { + throw new AnnotationFormatError("Unexpected end of annotations."); + } catch(IllegalArgumentException e) { + // Type mismatch in constant pool + throw new AnnotationFormatError(e); + } + } + + /** + * An overload of {@link #parseAnnotations(byte[], sun.reflect.ConstantPool, Class)} + * with an additional parameter {@code selectAnnotationClasses} which selects the + * annotation types to parse (other than selected are quickly skipped).

+ * This method is only used to parse select meta annotations in the construction + * phase of {@link AnnotationType} instances to prevent infinite recursion. + * + * @param selectAnnotationClasses an array of annotation types to select when parsing + */ + @SafeVarargs + public static Map, Annotation> parseAnnotations( + byte[] rawAnnotations, + ConstantPool constPool, + Class container, + Class ... selectAnnotationClasses) { + if (rawAnnotations == null) + return Collections.emptyMap(); + + try { + return parseAnnotations2(rawAnnotations, constPool, container, selectAnnotationClasses); } catch(BufferUnderflowException e) { throw new AnnotationFormatError("Unexpected end of annotations."); } catch(IllegalArgumentException e) { @@ -81,20 +109,21 @@ private static Map, Annotation> parseAnnotations2( byte[] rawAnnotations, ConstantPool constPool, - Class container) { + Class container, + Class[] selectAnnotationClasses) { Map, Annotation> result = new LinkedHashMap, Annotation>(); ByteBuffer buf = ByteBuffer.wrap(rawAnnotations); int numAnnotations = buf.getShort() & 0xFFFF; for (int i = 0; i < numAnnotations; i++) { - Annotation a = parseAnnotation(buf, constPool, container, false); + Annotation a = parseAnnotation(buf, constPool, container, false, selectAnnotationClasses); if (a != null) { Class klass = a.annotationType(); - AnnotationType type = AnnotationType.getInstance(klass); - if (type.retention() == RetentionPolicy.RUNTIME) - if (result.put(klass, a) != null) - throw new AnnotationFormatError( - "Duplicate annotation for class: "+klass+": " + a); + if (AnnotationType.getInstance(klass).retention() == RetentionPolicy.RUNTIME && + result.put(klass, a) != null) { + throw new AnnotationFormatError( + "Duplicate annotation for class: "+klass+": " + a); + } } } return result; @@ -151,7 +180,7 @@ List annotations = new ArrayList(numAnnotations); for (int j = 0; j < numAnnotations; j++) { - Annotation a = parseAnnotation(buf, constPool, container, false); + Annotation a = parseAnnotation(buf, constPool, container, false, null); if (a != null) { AnnotationType type = AnnotationType.getInstance( a.annotationType()); @@ -193,7 +222,8 @@ static Annotation parseAnnotation(ByteBuffer buf, ConstantPool constPool, Class container, - boolean exceptionOnMissingAnnotationClass) { + boolean exceptionOnMissingAnnotationClass, + Class[] selectAnnotationClasses) { int typeIndex = buf.getShort() & 0xFFFF; Class annotationClass = null; String sig = "[unknown]"; @@ -219,6 +249,10 @@ skipAnnotation(buf, false); return null; } + if (selectAnnotationClasses != null && !contains(selectAnnotationClasses, annotationClass)) { + skipAnnotation(buf, false); + return null; + } AnnotationType type = null; try { type = AnnotationType.getInstance(annotationClass); @@ -307,7 +341,7 @@ result = parseClassValue(buf, constPool, container); break; case '@': - result = parseAnnotation(buf, constPool, container, true); + result = parseAnnotation(buf, constPool, container, true, null); break; case '[': return parseArray(memberType, buf, constPool, container); @@ -720,7 +754,7 @@ for (int i = 0; i < length; i++) { tag = buf.get(); if (tag == '@') { - result[i] = parseAnnotation(buf, constPool, container, true); + result[i] = parseAnnotation(buf, constPool, container, true, null); } else { skipMemberValue(tag, buf); typeMismatch = true; @@ -800,6 +834,14 @@ skipMemberValue(buf); } + // utility + private static boolean contains(Object[] array, Object element) { + for (Object e : array) + if (e == element) + return true; + return false; + } + /* * This method converts the annotation map returned by the parseAnnotations() * method to an array. It is called by Field.getDeclaredAnnotations(), --- old/src/share/classes/sun/reflect/annotation/AnnotationType.java 2013-06-24 20:09:29.366570569 +0200 +++ new/src/share/classes/sun/reflect/annotation/AnnotationType.java 2013-06-24 20:09:29.253572603 +0200 @@ -25,6 +25,8 @@ package sun.reflect.annotation; +import sun.misc.JavaLangAccess; + import java.lang.annotation.*; import java.lang.reflect.*; import java.util.*; @@ -61,12 +63,12 @@ /** * The retention policy for this annotation type. */ - private RetentionPolicy retention = RetentionPolicy.RUNTIME;; + private final RetentionPolicy retention; /** * Whether this annotation type is inherited. */ - private boolean inherited = false; + private final boolean inherited; /** * Returns an AnnotationType instance for the specified annotation type. @@ -74,13 +76,16 @@ * @throw IllegalArgumentException if the specified class object for * does not represent a valid annotation type */ - public static synchronized AnnotationType getInstance( + public static AnnotationType getInstance( Class annotationClass) { - AnnotationType result = sun.misc.SharedSecrets.getJavaLangAccess(). - getAnnotationType(annotationClass); - if (result == null) - result = new AnnotationType((Class) annotationClass); + JavaLangAccess jla = sun.misc.SharedSecrets.getJavaLangAccess(); + AnnotationType result = jla.getAnnotationType(annotationClass); + if (result == null) { + result = new AnnotationType(annotationClass); + // multiple racy sets are idempotent (like in String.hashCode) + jla.setAnnotationType(annotationClass, result); + } return result; } @@ -121,16 +126,25 @@ memberDefaults.put(name, defaultValue); } - sun.misc.SharedSecrets.getJavaLangAccess(). - setAnnotationType(annotationClass, this); - // Initialize retention, & inherited fields. Special treatment // of the corresponding annotation types breaks infinite recursion. if (annotationClass != Retention.class && annotationClass != Inherited.class) { - Retention ret = annotationClass.getAnnotation(Retention.class); + JavaLangAccess jla = sun.misc.SharedSecrets.getJavaLangAccess(); + Map, Annotation> metaAnnotations = + AnnotationParser.parseAnnotations( + jla.getRawClassAnnotations(annotationClass), + jla.getConstantPool(annotationClass), + annotationClass, + Retention.class, Inherited.class + ); + Retention ret = (Retention) metaAnnotations.get(Retention.class); retention = (ret == null ? RetentionPolicy.CLASS : ret.value()); - inherited = annotationClass.isAnnotationPresent(Inherited.class); + inherited = metaAnnotations.containsKey(Inherited.class); + } + else { + retention = RetentionPolicy.RUNTIME; + inherited = false; } } @@ -205,11 +219,10 @@ * For debugging. */ public String toString() { - StringBuffer s = new StringBuffer("Annotation Type:" + "\n"); - s.append(" Member types: " + memberTypes + "\n"); - s.append(" Member defaults: " + memberDefaults + "\n"); - s.append(" Retention policy: " + retention + "\n"); - s.append(" Inherited: " + inherited); - return s.toString(); + return "Annotation Type:\n" + + " Member types: " + memberTypes + "\n" + + " Member defaults: " + memberDefaults + "\n" + + " Retention policy: " + retention + "\n" + + " Inherited: " + inherited; } } --- /dev/null 2013-06-24 17:10:30.622000071 +0200 +++ new/test/java/lang/annotation/AnnotationType/AnnotationTypeDeadlockTest.java 2013-06-24 20:09:29.545567347 +0200 @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2013, 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 7122142 + * @summary Test deadlock situation when recursive annotations are parsed + */ + +import java.lang.annotation.Retention; +import java.util.concurrent.atomic.AtomicInteger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +public class AnnotationTypeDeadlockTest { + + @Retention(RUNTIME) + @AnnB + public @interface AnnA { + } + + @Retention(RUNTIME) + @AnnA + public @interface AnnB { + } + + static class Task extends Thread { + final AtomicInteger latch; + final Class clazz; + + Task(AtomicInteger latch, Class clazz) { + super(clazz.getSimpleName()); + setDaemon(true); // in case it deadlocks + this.latch = latch; + this.clazz = clazz; + } + + @Override + public void run() { + latch.incrementAndGet(); + while (latch.get() > 0) ; // spin-wait + clazz.getDeclaredAnnotations(); + } + } + + static void dumpState(Task task) { + System.err.println( + "Task[" + task.getName() + "].state: " + + task.getState() + " ..." + ); + for (StackTraceElement ste : task.getStackTrace()) { + System.err.println("\tat " + ste); + } + System.err.println(); + } + + public static void main(String[] args) throws Exception { + AtomicInteger latch = new AtomicInteger(); + Task taskA = new Task(latch, AnnA.class); + Task taskB = new Task(latch, AnnB.class); + taskA.start(); + taskB.start(); + // spin-wait for both threads to start-up + while (latch.get() < 2) ; + // trigger coherent start + latch.set(0); + // join them + taskA.join(500L); + taskB.join(500L); + + if (taskA.isAlive() || taskB.isAlive()) { + dumpState(taskA); + dumpState(taskB); + throw new IllegalStateException( + taskA.getState() == Thread.State.BLOCKED && + taskB.getState() == Thread.State.BLOCKED + ? "deadlock detected" + : "unexpected condition"); + } + } +} --- /dev/null 2013-06-24 17:10:30.622000071 +0200 +++ new/test/java/lang/annotation/AnnotationType/AnnotationTypeRuntimeAssumptionTest.java 2013-06-24 20:09:29.802562720 +0200 @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2013, 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 + * @summary Test consistent parsing of ex RUNTIME annotations that + * were changed and separately compiled to have CLASS retention + */ + +import sun.misc.IOUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import static java.lang.annotation.RetentionPolicy.CLASS; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * This test simulates a situation where there are two mutually recursive + * {@link RetentionPolicy#RUNTIME RUNTIME} annotations {@link AnnA_v1 AnnA_v1} + * and {@link AnnB AnnB} and then the first is changed to have {@link RetentionPolicy#CLASS CLASS} + * retention and separately compiled. When {@link AnnA_v1 AnnA_v1} annotation si looked-up on + * {@link AnnB AnnB} it still appears to have {@link RetentionPolicy#RUNTIME RUNTIME} + * retention. + */ +public class AnnotationTypeRuntimeAssumptionTest { + + @Retention(RUNTIME) + @AnnB + public @interface AnnA_v1 { + } + + // an alternative version of AnnA_v1 with CLASS retention instead + // used to simulate separate compilation (see AltClassLoader below) + @Retention(CLASS) + @AnnB + public @interface AnnA_v2 { + } + + @Retention(RUNTIME) + @AnnA_v1 + public @interface AnnB { + } + + @AnnA_v1 + public static class TestTask implements Runnable { + @Override + public void run() { + AnnA_v1 ann1 = TestTask.class.getDeclaredAnnotation(AnnA_v1.class); + if (ann1 != null) { + throw new IllegalStateException("@" + ann1.annotationType().getSimpleName() + + " found on: " + TestTask.class.getName() + + " should not be visible at runtime"); + } + AnnA_v1 ann2 = AnnB.class.getDeclaredAnnotation(AnnA_v1.class); + if (ann2 != null) { + throw new IllegalStateException("@" + ann2.annotationType().getSimpleName() + + " found on: " + AnnB.class.getName() + + " should not be visible at runtime"); + } + } + } + + public static void main(String[] args) throws Exception { + ClassLoader altLoader = new AltClassLoader(AnnotationTypeRuntimeAssumptionTest.class.getClassLoader()); + Runnable altTask = (Runnable) Class.forName(TestTask.class.getName(), true, altLoader).newInstance(); + altTask.run(); + } + + /** + * A ClassLoader implementation that loads alternative implementations of classes instead of original. + * If class name ends with "_v1" it locates instead class with name ending with "_v2" and then renames + * the class bytes to the original name before defining the class. + */ + static class AltClassLoader extends ClassLoader { + AltClassLoader(ClassLoader parent) { + super(parent); + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (name.indexOf('.') < 0) { // root package is our class + synchronized (getClassLoadingLock(name)) { + // First, check if the class has already been loaded + Class c = findLoadedClass(name); + if (c == null) { + c = findClass(name); + } + if (resolve) { + resolveClass(c); + } + return c; + } + } + else { // not our class + return super.loadClass(name, resolve); + } + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (name.endsWith("_v1")) { // special class name -> replace it with alternative name + String altName = name.substring(0, name.length() - 3) + "_v2"; + String altPath = altName.replace('.', '/').concat(".class"); + try (InputStream is = getResourceAsStream(altPath)) { + if (is != null) { + byte[] bytes = IOUtils.readFully(is, -1, true); + // patch class bytes to contain original (not alternative) name + for (int i = 0; i < bytes.length - 2; i++) { + if (bytes[i] == '_' && bytes[i + 1] == 'v' && bytes[i + 2] == '2') { + bytes[i + 2] = '1'; + } + } + return defineClass(name, bytes, 0, bytes.length); + } + else { + throw new ClassNotFoundException(name); + } + } + catch (IOException e) { + throw new ClassNotFoundException(name, e); + } + } + else { // no special class name -> just load the class by original name + String path = name.replace('.', '/').concat(".class"); + try (InputStream is = getResourceAsStream(path)) { + if (is != null) { + byte[] bytes = IOUtils.readFully(is, -1, true); + return defineClass(name, bytes, 0, bytes.length); + } + else { + throw new ClassNotFoundException(name); + } + } + catch (IOException e) { + throw new ClassNotFoundException(name, e); + } + } + } + } +} \ No newline at end of file