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
36import static jdk.test.lib.Asserts.assertTrue;
37import java.io.FileNotFoundException;
38import java.io.PrintWriter;
39import java.lang.NoSuchFieldException;
40import java.lang.NoSuchMethodException;
41import java.lang.RuntimeException;
42import java.lang.annotation.Annotation;
43import java.lang.annotation.ElementType;
44import java.lang.annotation.Retention;
45import java.lang.annotation.RetentionPolicy;
46import java.lang.annotation.Target;
47import java.lang.instrument.ClassFileTransformer;
48import java.lang.instrument.IllegalClassFormatException;
49import java.lang.instrument.Instrumentation;
50import java.lang.instrument.UnmodifiableClassException;
51import java.lang.reflect.AnnotatedArrayType;
52import java.lang.reflect.AnnotatedParameterizedType;
53import java.lang.reflect.AnnotatedType;
54import java.lang.reflect.AnnotatedWildcardType;
55import java.lang.reflect.Executable;
56import java.lang.reflect.TypeVariable;
57import java.security.ProtectionDomain;
58import java.util.Arrays;
59import java.util.LinkedList;
60import java.util.List;
61import java.util.Map;
62import jdk.internal.org.objectweb.asm.ClassReader;
63import jdk.internal.org.objectweb.asm.ClassVisitor;
64import jdk.internal.org.objectweb.asm.ClassWriter;
65import jdk.internal.org.objectweb.asm.FieldVisitor;
66import static jdk.internal.org.objectweb.asm.Opcodes.ASM5;
67
68@Retention(RetentionPolicy.RUNTIME)
69@Target(ElementType.TYPE_USE)
70@interface TestAnn {
71    String site();
72}
73
74public 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(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
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}
415