Mercurial > hg > truffle
comparison test/runtime/RedefineTests/RedefineAnnotations.java @ 20613:50054b63f0aa
8057043: Type annotations not retained during class redefine / retransform
Reviewed-by: coleenp, sspitsyn, jfranck
author | aeriksso |
---|---|
date | Wed, 22 Oct 2014 13:59:56 +0200 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
20611:3c87c13918fb | 20613:50054b63f0aa |
---|---|
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 } |