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