1 /* 2 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @library /testlibrary 27 * @summary Test that type annotations are retained after a retransform 28 * @run main RedefineAnnotations buildagent 29 * @run main/othervm -javaagent:redefineagent.jar RedefineAnnotations 30 */ 31 32 import static com.oracle.java.testlibrary.Asserts.assertTrue; 33 import java.io.FileNotFoundException; 34 import java.io.PrintWriter; 35 import java.lang.NoSuchFieldException; 36 import java.lang.NoSuchMethodException; 37 import java.lang.RuntimeException; 38 import java.lang.annotation.Annotation; 39 import java.lang.annotation.ElementType; 40 import java.lang.annotation.Retention; 41 import java.lang.annotation.RetentionPolicy; 42 import java.lang.annotation.Target; 43 import java.lang.instrument.ClassFileTransformer; 44 import java.lang.instrument.IllegalClassFormatException; 45 import java.lang.instrument.Instrumentation; 46 import java.lang.instrument.UnmodifiableClassException; 47 import java.lang.reflect.AnnotatedArrayType; 48 import java.lang.reflect.AnnotatedParameterizedType; 49 import java.lang.reflect.AnnotatedType; 50 import java.lang.reflect.AnnotatedWildcardType; 51 import java.lang.reflect.Executable; 52 import java.lang.reflect.TypeVariable; 53 import java.security.ProtectionDomain; 54 import java.util.Arrays; 55 import java.util.LinkedList; 56 import java.util.List; 57 import java.util.Map; 58 import jdk.internal.org.objectweb.asm.ClassReader; 59 import jdk.internal.org.objectweb.asm.ClassVisitor; 60 import jdk.internal.org.objectweb.asm.ClassWriter; 61 import jdk.internal.org.objectweb.asm.FieldVisitor; 62 import static jdk.internal.org.objectweb.asm.Opcodes.ASM5; 63 64 @Retention(RetentionPolicy.RUNTIME) 65 @Target(ElementType.TYPE_USE) 66 @interface TestAnn { 67 String site(); 68 } 69 70 public class RedefineAnnotations { 71 static Instrumentation inst; 72 public static void premain(String agentArgs, Instrumentation inst) { 73 RedefineAnnotations.inst = inst; 74 } 75 76 static class Transformer implements ClassFileTransformer { 77 78 public byte[] asm(ClassLoader loader, String className, 79 Class<?> classBeingRedefined, 80 ProtectionDomain protectionDomain, byte[] classfileBuffer) 81 throws IllegalClassFormatException { 82 83 ClassWriter cw = new ClassWriter(0); 84 ClassVisitor cv = new ReAddDummyFieldsClassVisitor(ASM5, cw) { }; 85 ClassReader cr = new ClassReader(classfileBuffer); 86 cr.accept(cv, 0); 87 return cw.toByteArray(); 88 } 89 90 public class ReAddDummyFieldsClassVisitor extends ClassVisitor { 91 92 LinkedList<F> fields = new LinkedList<>(); 93 94 public ReAddDummyFieldsClassVisitor(int api, ClassVisitor cv) { 95 super(api, cv); 96 } 97 98 @Override public FieldVisitor visitField(int access, String name, 99 String desc, String signature, Object value) { 100 if (name.startsWith("dummy")) { 101 // Remove dummy field 102 fields.addLast(new F(access, name, desc, signature, value)); 103 return null; 104 } 105 return cv.visitField(access, name, desc, signature, value); 106 } 107 108 @Override public void visitEnd() { 109 F f; 110 while ((f = fields.pollFirst()) != null) { 111 // Re-add dummy fields 112 cv.visitField(f.access, f.name, f.desc, f.signature, f.value); 113 } 114 } 115 116 private class F { 117 private int access; 118 private String name; 119 private String desc; 120 private String signature; 121 private Object value; 122 F(int access, String name, String desc, String signature, Object value) { 123 this.access = access; 124 this.name = name; 125 this.desc = desc; 126 this.signature = signature; 127 this.value = value; 128 } 129 } 130 } 131 132 @Override public byte[] transform(ClassLoader loader, String className, 133 Class<?> classBeingRedefined, 134 ProtectionDomain protectionDomain, byte[] classfileBuffer) 135 throws IllegalClassFormatException { 136 137 if (className.contains("TypeAnnotatedTestClass")) { 138 try { 139 // Here we remove and re-add the dummy fields. This shuffles the constant pool 140 return asm(loader, className, classBeingRedefined, protectionDomain, classfileBuffer); 141 } catch (Throwable e) { 142 // The retransform native code that called this method does not propagate 143 // exceptions. Instead of getting an uninformative generic error, catch 144 // problems here and print it, then exit. 145 e.printStackTrace(); 146 System.exit(1); 147 } 148 } 149 return null; 150 } 151 } 152 153 private static void buildAgent() { 154 try { 155 ClassFileInstaller.main("RedefineAnnotations"); 156 } catch (Exception e) { 157 throw new RuntimeException("Could not write agent classfile", e); 158 } 159 160 try { 161 PrintWriter pw = new PrintWriter("MANIFEST.MF"); 162 pw.println("Premain-Class: RedefineAnnotations"); 163 pw.println("Agent-Class: RedefineAnnotations"); 164 pw.println("Can-Retransform-Classes: true"); 165 pw.close(); 166 } catch (FileNotFoundException e) { 167 throw new RuntimeException("Could not write manifest file for the agent", e); 168 } 169 170 sun.tools.jar.Main jarTool = new sun.tools.jar.Main(System.out, System.err, "jar"); 171 if (!jarTool.run(new String[] { "-cmf", "MANIFEST.MF", "redefineagent.jar", "RedefineAnnotations.class" })) { 172 throw new RuntimeException("Could not write the agent jar file"); 173 } 174 } 175 176 public static void main(String argv[]) throws NoSuchFieldException, NoSuchMethodException { 177 if (argv.length == 1 && argv[0].equals("buildagent")) { 178 buildAgent(); 179 return; 180 } 181 182 if (inst == null) { 183 throw new RuntimeException("Instrumentation object was null"); 184 } 185 186 RedefineAnnotations test = new RedefineAnnotations(); 187 test.testTransformAndVerify(); 188 } 189 190 // Class type annotations 191 private Annotation classTypeParameterTA; 192 private Annotation extendsTA; 193 private Annotation implementsTA; 194 195 // Field type annotations 196 private Annotation fieldTA; 197 private Annotation innerTA; 198 private Annotation[] arrayTA = new Annotation[4]; 199 private Annotation[] mapTA = new Annotation[5]; 200 201 // Method type annotations 202 private Annotation returnTA, methodTypeParameterTA, formalParameterTA, throwsTA; 203 204 private void testTransformAndVerify() 205 throws NoSuchFieldException, NoSuchMethodException { 206 207 Class<TypeAnnotatedTestClass> c = TypeAnnotatedTestClass.class; 208 Class<?> myClass = c; 209 210 /* 211 * Verify that the expected annotations are where they should be before transform. 212 */ 213 verifyClassTypeAnnotations(c); 214 verifyFieldTypeAnnotations(c); 215 verifyMethodTypeAnnotations(c); 216 217 try { 218 inst.addTransformer(new Transformer(), true); 219 inst.retransformClasses(myClass); 220 } catch (UnmodifiableClassException e) { 221 throw new RuntimeException(e); 222 } 223 224 /* 225 * Verify that the expected annotations are where they should be after transform. 226 * Also verify that before and after are equal. 227 */ 228 verifyClassTypeAnnotations(c); 229 verifyFieldTypeAnnotations(c); 230 verifyMethodTypeAnnotations(c); 231 } 232 233 private void verifyClassTypeAnnotations(Class c) { 234 Annotation anno; 235 236 anno = c.getTypeParameters()[0].getAnnotations()[0]; 237 verifyTestAnn(classTypeParameterTA, anno, "classTypeParameter"); 238 classTypeParameterTA = anno; 239 240 anno = c.getAnnotatedSuperclass().getAnnotations()[0]; 241 verifyTestAnn(extendsTA, anno, "extends"); 242 extendsTA = anno; 243 244 anno = c.getAnnotatedInterfaces()[0].getAnnotations()[0]; 245 verifyTestAnn(implementsTA, anno, "implements"); 246 implementsTA = anno; 247 } 248 249 private void verifyFieldTypeAnnotations(Class c) 250 throws NoSuchFieldException, NoSuchMethodException { 251 252 verifyBasicFieldTypeAnnotations(c); 253 verifyInnerFieldTypeAnnotations(c); 254 verifyArrayFieldTypeAnnotations(c); 255 verifyMapFieldTypeAnnotations(c); 256 } 257 258 private void verifyBasicFieldTypeAnnotations(Class c) 259 throws NoSuchFieldException, NoSuchMethodException { 260 261 Annotation anno = c.getDeclaredField("typeAnnotatedBoolean").getAnnotatedType().getAnnotations()[0]; 262 verifyTestAnn(fieldTA, anno, "field"); 263 fieldTA = anno; 264 } 265 266 private void verifyInnerFieldTypeAnnotations(Class c) 267 throws NoSuchFieldException, NoSuchMethodException { 268 269 AnnotatedType at = c.getDeclaredField("typeAnnotatedInner").getAnnotatedType(); 270 Annotation anno = at.getAnnotations()[0]; 271 verifyTestAnn(innerTA, anno, "inner"); 272 innerTA = anno; 273 } 274 275 private void verifyArrayFieldTypeAnnotations(Class c) 276 throws NoSuchFieldException, NoSuchMethodException { 277 278 Annotation anno; 279 AnnotatedType at; 280 281 at = c.getDeclaredField("typeAnnotatedArray").getAnnotatedType(); 282 anno = at.getAnnotations()[0]; 283 verifyTestAnn(arrayTA[0], anno, "array1"); 284 arrayTA[0] = anno; 285 286 for (int i = 1; i <= 3; i++) { 287 at = ((AnnotatedArrayType) at).getAnnotatedGenericComponentType(); 288 anno = at.getAnnotations()[0]; 289 verifyTestAnn(arrayTA[i], anno, "array" + (i + 1)); 290 arrayTA[i] = anno; 291 } 292 } 293 294 private void verifyMapFieldTypeAnnotations(Class c) 295 throws NoSuchFieldException, NoSuchMethodException { 296 297 Annotation anno; 298 AnnotatedType atBase; 299 AnnotatedType atParameter; 300 atBase = c.getDeclaredField("typeAnnotatedMap").getAnnotatedType(); 301 302 anno = atBase.getAnnotations()[0]; 303 verifyTestAnn(mapTA[0], anno, "map1"); 304 mapTA[0] = anno; 305 306 atParameter = 307 ((AnnotatedParameterizedType) atBase). 308 getAnnotatedActualTypeArguments()[0]; 309 anno = ((AnnotatedWildcardType) atParameter).getAnnotations()[0]; 310 verifyTestAnn(mapTA[1], anno, "map2"); 311 mapTA[1] = anno; 312 313 anno = 314 ((AnnotatedWildcardType) atParameter). 315 getAnnotatedUpperBounds()[0].getAnnotations()[0]; 316 verifyTestAnn(mapTA[2], anno, "map3"); 317 mapTA[2] = anno; 318 319 atParameter = 320 ((AnnotatedParameterizedType) atBase). 321 getAnnotatedActualTypeArguments()[1]; 322 anno = ((AnnotatedParameterizedType) atParameter).getAnnotations()[0]; 323 verifyTestAnn(mapTA[3], anno, "map4"); 324 mapTA[3] = anno; 325 326 anno = 327 ((AnnotatedParameterizedType) atParameter). 328 getAnnotatedActualTypeArguments()[0].getAnnotations()[0]; 329 verifyTestAnn(mapTA[4], anno, "map5"); 330 mapTA[4] = anno; 331 } 332 333 private void verifyMethodTypeAnnotations(Class c) 334 throws NoSuchFieldException, NoSuchMethodException { 335 Annotation anno; 336 Executable typeAnnotatedMethod = 337 c.getDeclaredMethod("typeAnnotatedMethod", TypeAnnotatedTestClass.class); 338 339 anno = typeAnnotatedMethod.getAnnotatedReturnType().getAnnotations()[0]; 340 verifyTestAnn(returnTA, anno, "return"); 341 returnTA = anno; 342 343 anno = typeAnnotatedMethod.getTypeParameters()[0].getAnnotations()[0]; 344 verifyTestAnn(methodTypeParameterTA, anno, "methodTypeParameter"); 345 methodTypeParameterTA = anno; 346 347 anno = typeAnnotatedMethod.getAnnotatedParameterTypes()[0].getAnnotations()[0]; 348 verifyTestAnn(formalParameterTA, anno, "formalParameter"); 349 formalParameterTA = anno; 350 351 anno = typeAnnotatedMethod.getAnnotatedExceptionTypes()[0].getAnnotations()[0]; 352 verifyTestAnn(throwsTA, anno, "throws"); 353 throwsTA = anno; 354 } 355 356 private static void verifyTestAnn(Annotation verifyAgainst, Annotation anno, String expectedSite) { 357 verifyTestAnnSite(anno, expectedSite); 358 359 // When called before transform verifyAgainst will be null, when called 360 // after transform it will be the annotation from before the transform 361 if (verifyAgainst != null) { 362 assertTrue(anno.equals(verifyAgainst), 363 "Annotations do not match before and after." + 364 " Before: \"" + verifyAgainst + "\", After: \"" + anno + "\""); 365 } 366 } 367 368 private static void verifyTestAnnSite(Annotation testAnn, String expectedSite) { 369 String expectedAnn = "@TestAnn(site=" + expectedSite + ")"; 370 assertTrue(testAnn.toString().equals(expectedAnn), 371 "Expected \"" + expectedAnn + "\", got \"" + testAnn + "\""); 372 } 373 374 public static class TypeAnnotatedTestClass <@TestAnn(site="classTypeParameter") S,T> 375 extends @TestAnn(site="extends") Thread 376 implements @TestAnn(site="implements") Runnable { 377 378 public @TestAnn(site="field") boolean typeAnnotatedBoolean; 379 380 public 381 RedefineAnnotations. 382 @TestAnn(site="inner") TypeAnnotatedTestClass 383 typeAnnotatedInner; 384 385 public 386 @TestAnn(site="array4") boolean 387 @TestAnn(site="array1") [] 388 @TestAnn(site="array2") [] 389 @TestAnn(site="array3") [] 390 typeAnnotatedArray; 391 392 public @TestAnn(site="map1") Map 393 <@TestAnn(site="map2") ? extends @TestAnn(site="map3") String, 394 @TestAnn(site="map4") List<@TestAnn(site="map5") Object>> typeAnnotatedMap; 395 396 public int dummy1; 397 public int dummy2; 398 public int dummy3; 399 400 @TestAnn(site="return") <@TestAnn(site="methodTypeParameter") U,V> Class 401 typeAnnotatedMethod(@TestAnn(site="formalParameter") TypeAnnotatedTestClass arg) 402 throws @TestAnn(site="throws") ClassNotFoundException { 403 404 @TestAnn(site="local_variable_type") int foo = 0; 405 throw new ClassNotFoundException(); 406 } 407 408 public void run() {} 409 } 410 }