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.ASM7;
  67 
  68 @Retention(RetentionPolicy.RUNTIME)
  69 @Target(ElementType.TYPE_USE)
  70 @interface TestAnn {
  71     String site();
  72 }
  73 
  74 public class RedefineAnnotations {
  75     static Instrumentation inst;
  76     public static void premain(String agentArgs, Instrumentation inst) {
  77         RedefineAnnotations.inst = inst;
  78     }
  79 
  80     static class Transformer implements ClassFileTransformer {
  81 
  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(ASM7, 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 
  98             public ReAddDummyFieldsClassVisitor(int api, ClassVisitor cv) {
  99                 super(api, cv);
 100             }
 101 
 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 
 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;
 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 
 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 
 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 
 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 
 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 
 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 
 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 
 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 
 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 
 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 
 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 
 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 
 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 
 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;
 409             throw new ClassNotFoundException();
 410         }
 411 
 412         public void run() {}
 413     }
 414 }