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 }