1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package net.grinder.util.weave.j2se6;
23
24 import static net.grinder.testutility.AssertUtilities.assertArraysEqual;
25
26 import java.io.ByteArrayOutputStream;
27 import java.io.IOException;
28 import java.io.ObjectOutputStream;
29 import java.io.Serializable;
30 import java.lang.instrument.ClassFileTransformer;
31 import java.lang.instrument.Instrumentation;
32 import java.lang.reflect.Constructor;
33 import java.lang.reflect.Member;
34 import java.lang.reflect.Method;
35 import java.lang.reflect.Modifier;
36 import java.util.ArrayList;
37 import java.util.HashMap;
38 import java.util.List;
39 import java.util.Map;
40
41 import junit.framework.TestCase;
42 import net.grinder.testutility.CallData;
43 import net.grinder.testutility.CallRecorder;
44 import net.grinder.testutility.RandomStubFactory;
45 import net.grinder.util.weave.Weaver.TargetSource;
46 import net.grinder.util.weave.WeavingException;
47 import net.grinder.util.weave.agent.ExposeInstrumentation;
48 import net.grinder.util.weave.j2se6.DCRWeaver.ClassFileTransformerFactory;
49
50
51
52
53
54
55
56 public class TestASMTransformerFactory extends TestCase {
57
58 private final PointCutRegistryStubFactory m_pointCutRegistryStubFactory =
59 new PointCutRegistryStubFactory();
60 private final PointCutRegistry m_pointCutRegistry =
61 m_pointCutRegistryStubFactory.getStub();
62
63 @Override protected void tearDown() throws Exception {
64 super.tearDown();
65 m_pointCutRegistryStubFactory.clear();
66 }
67
68 private static final CallRecorder s_callRecorder = new CallRecorder();
69
70 public void testFactory() throws Exception {
71 final ClassFileTransformerFactory transformerFactory =
72 new ASMTransformerFactory(MyAdvice.class);
73
74 assertNotNull(transformerFactory.create(m_pointCutRegistry));
75 }
76
77 public void testFactoryWithBadAdvice() throws Exception {
78 try {
79 new ASMTransformerFactory(BadAdvice1.class);
80 fail("Expected WeavingException");
81 }
82 catch (WeavingException e) {
83 assertTrue(e.getCause() instanceof NoSuchMethodException);
84 }
85
86 try {
87 new ASMTransformerFactory(BadAdvice2.class);
88 fail("Expected WeavingException");
89 }
90 catch (WeavingException e) {
91 assertTrue(e.getCause() instanceof NoSuchMethodException);
92 }
93
94 try {
95 new ASMTransformerFactory(BadAdvice3.class);
96 fail("Expected WeavingException");
97 }
98 catch (WeavingException e) {
99 }
100
101 try {
102 new ASMTransformerFactory(BadAdvice4.class);
103 fail("Expected WeavingException");
104 }
105 catch (WeavingException e) {
106 }
107 }
108
109 private static final Instrumentation getInstrumentation() {
110 final Instrumentation instrumentation =
111 ExposeInstrumentation.getInstrumentation();
112
113 assertNotNull(
114 "Instrumentation is not available, " +
115 "please add -javaagent:grinder-agent.jar to the command line",
116 instrumentation);
117
118 return instrumentation;
119 }
120
121 public void testWithAgent() throws Exception {
122 final Instrumentation instrumentation = getInstrumentation();
123 assertTrue(instrumentation.isRetransformClassesSupported());
124
125 final ClassFileTransformerFactory transformerFactory =
126 new ASMTransformerFactory(MyAdvice.class);
127
128 m_pointCutRegistryStubFactory.addMethod(A.class, "m1", "loc1");
129 m_pointCutRegistryStubFactory.addMethod(A.class, "m2", "loc2");
130 m_pointCutRegistryStubFactory.addMethod(A.class, "m4", "loc4");
131
132 final ClassFileTransformer transformer =
133 transformerFactory.create(m_pointCutRegistry);
134
135 final A anotherA = new A();
136 anotherA.m1();
137 s_callRecorder.assertNoMoreCalls();
138
139 instrumentation.addTransformer(transformer, true);
140 instrumentation.retransformClasses(new Class[] { A.class, A2.class });
141
142 final A a = new A();
143 assertEquals(1, a.m1());
144
145 s_callRecorder.assertSuccess("enter", a, "loc1");
146 s_callRecorder.assertSuccess("exit", a, "loc1", true);
147 s_callRecorder.assertNoMoreCalls();
148
149 anotherA.m1();
150 s_callRecorder.assertSuccess("enter", anotherA, "loc1");
151 s_callRecorder.assertSuccess("exit", anotherA, "loc1", true);
152 s_callRecorder.assertNoMoreCalls();
153
154 try {
155 a.m2();
156 fail("Expected RuntimeException");
157 }
158 catch (RuntimeException e) {
159 }
160
161 s_callRecorder.assertSuccess("enter", a, "loc2");
162 s_callRecorder.assertSuccess("exit", a, "loc2", false);
163 s_callRecorder.assertNoMoreCalls();
164
165 try {
166 a.m4();
167 fail("Expected RuntimeException");
168 }
169 catch (RuntimeException e) {
170 }
171
172 s_callRecorder.assertSuccess("enter", a, "loc4");
173 s_callRecorder.assertSuccess("enter", a, "loc2");
174 s_callRecorder.assertSuccess("exit", a, "loc2", false);
175 s_callRecorder.assertSuccess("exit", a, "loc4", false);
176 s_callRecorder.assertNoMoreCalls();
177
178 m_pointCutRegistryStubFactory.addMethod(A.class, "m1", "locX");
179
180 instrumentation.retransformClasses(new Class[] { A.class, A2.class });
181
182 a.m1();
183
184 s_callRecorder.assertSuccess("enter", a, "loc1");
185 s_callRecorder.assertSuccess("enter", a, "locX");
186 s_callRecorder.assertSuccess("exit", a, "locX", true);
187 s_callRecorder.assertSuccess("exit", a, "loc1", true);
188 s_callRecorder.assertNoMoreCalls();
189
190 instrumentation.removeTransformer(transformer);
191 }
192
193 public void testTwoTransformations() throws Exception {
194 final Instrumentation instrumentation = getInstrumentation();
195
196 final ClassFileTransformerFactory transformerFactory =
197 new ASMTransformerFactory(MyAdvice.class);
198
199 m_pointCutRegistryStubFactory.addMethod(A.class, "m1", "loc1");
200 m_pointCutRegistryStubFactory.addMethod(A.class, "m2", "loc2");
201
202 final ClassFileTransformer transformer1 =
203 transformerFactory.create(m_pointCutRegistry);
204 final ClassFileTransformer transformer2 =
205 transformerFactory.create(m_pointCutRegistry);
206
207 instrumentation.addTransformer(transformer1, true);
208 instrumentation.addTransformer(transformer2, true);
209
210 instrumentation.retransformClasses(new Class[] { A.class, });
211
212
213 final A a = new A();
214 assertEquals(1, a.m1());
215
216 s_callRecorder.assertSuccess("enter", a, "loc1");
217 s_callRecorder.assertSuccess("enter", a, "loc1");
218 s_callRecorder.assertSuccess("exit", a, "loc1", true);
219 s_callRecorder.assertSuccess("exit", a, "loc1", true);
220 s_callRecorder.assertNoMoreCalls();
221
222 try {
223 a.m2();
224 fail("Expected RuntimeException");
225 }
226 catch (RuntimeException e) {
227 }
228
229 s_callRecorder.assertSuccess("enter", a, "loc2");
230 s_callRecorder.assertSuccess("enter", a, "loc2");
231 s_callRecorder.assertSuccess("exit", a, "loc2", false);
232 s_callRecorder.assertSuccess("exit", a, "loc2", false);
233 s_callRecorder.assertNoMoreCalls();
234
235 assertTrue(instrumentation.removeTransformer(transformer2));
236 assertTrue(instrumentation.removeTransformer(transformer1));
237 instrumentation.retransformClasses(new Class[] { A.class, });
238 s_callRecorder.assertNoMoreCalls();
239
240 instrumentation.removeTransformer(transformer1);
241 instrumentation.removeTransformer(transformer2);
242 }
243
244 public void testSerializationNotBroken() throws Exception {
245 final Instrumentation instrumentation = getInstrumentation();
246
247 final ClassFileTransformerFactory transformerFactory =
248 new ASMTransformerFactory(MyAdvice.class);
249
250 m_pointCutRegistryStubFactory.addMethod(SerializableA.class, "m1", "loc1");
251
252 final ClassFileTransformer transformer =
253 transformerFactory.create(m_pointCutRegistry);
254
255 final SerializableA a = new SerializableA();
256
257 assertEquals(1, a.m1());
258 s_callRecorder.assertNoMoreCalls();
259
260 final byte[] originalBytes = serialize(a);
261
262 instrumentation.addTransformer(transformer, true);
263 instrumentation.retransformClasses(new Class[] { SerializableA.class, });
264
265 assertEquals(1, a.m1());
266
267 s_callRecorder.assertSuccess("enter", a, "loc1");
268 s_callRecorder.assertSuccess("exit", a, "loc1", true);
269 s_callRecorder.assertNoMoreCalls();
270
271 final byte[] bytes = serialize(a);
272
273 assertArraysEqual(originalBytes, bytes);
274
275 instrumentation.removeTransformer(transformer);
276 }
277
278 public void testConstructors() throws Exception {
279 final Instrumentation instrumentation = getInstrumentation();
280
281 final ClassFileTransformerFactory transformerFactory =
282 new ASMTransformerFactory(MyAdvice.class);
283
284 m_pointCutRegistryStubFactory.addConstructor(
285 A2.class, A2.class.getDeclaredConstructor(Integer.TYPE), "loc1");
286
287 final ClassFileTransformer transformer =
288 transformerFactory.create(m_pointCutRegistry);
289
290 new A2(1);
291 s_callRecorder.assertNoMoreCalls();
292
293 instrumentation.addTransformer(transformer, true);
294 instrumentation.retransformClasses(new Class[] { A2.class, });
295
296 new A2(1);
297
298 s_callRecorder.assertSuccess("enter", A2.class, "loc1");
299 s_callRecorder.assertSuccess("exit", A2.class, "loc1", true);
300 s_callRecorder.assertNoMoreCalls();
301
302 new A3();
303 s_callRecorder.assertNoMoreCalls();
304
305 m_pointCutRegistryStubFactory.addConstructor(
306 A3.class, A3.class.getDeclaredConstructor(), "loc2");
307 instrumentation.retransformClasses(new Class[] { A3.class, A2.class });
308
309 new A3();
310
311 s_callRecorder.assertSuccess("enter", A3.class, "loc2");
312 s_callRecorder.assertSuccess("exit", A3.class, "loc2", true);
313 s_callRecorder.assertNoMoreCalls();
314
315 instrumentation.removeTransformer(transformer);
316 }
317
318 public void testOverloading() throws Exception {
319 final Instrumentation instrumentation = getInstrumentation();
320
321 final ClassFileTransformerFactory transformerFactory =
322 new ASMTransformerFactory(MyAdvice.class);
323
324 m_pointCutRegistryStubFactory.addConstructor(
325 A4.class, A4.class.getDeclaredConstructor(), "loc1");
326 m_pointCutRegistryStubFactory.addConstructor(
327 A4.class, A4.class.getDeclaredConstructor(String.class), "loc2");
328
329 m_pointCutRegistryStubFactory.addMethod(
330 A4.class, A4.class.getDeclaredMethod("m1", Integer.TYPE), "loc3");
331 m_pointCutRegistryStubFactory.addMethod(
332 A4.class, A4.class.getDeclaredMethod("m1", String.class), "loc4");
333
334 final ClassFileTransformer transformer =
335 transformerFactory.create(m_pointCutRegistry);
336
337 instrumentation.addTransformer(transformer, true);
338 instrumentation.retransformClasses(new Class[] { A4.class, });
339
340 final A4 a = new A4("abc");
341
342 s_callRecorder.assertSuccess("enter", A4.class, "loc2");
343 s_callRecorder.assertSuccess("enter", A4.class, "loc1");
344 s_callRecorder.assertSuccess("exit", A4.class, "loc1", true);
345 s_callRecorder.assertSuccess("exit", A4.class, "loc2", true);
346 s_callRecorder.assertNoMoreCalls();
347
348 a.m1(1);
349
350 s_callRecorder.assertSuccess("enter", a, "loc3");
351 s_callRecorder.assertSuccess("enter", a, "loc4");
352 s_callRecorder.assertSuccess("exit", a, "loc4", true);
353 s_callRecorder.assertSuccess("exit", a, "loc3", true);
354 s_callRecorder.assertNoMoreCalls();
355
356 instrumentation.removeTransformer(transformer);
357 }
358
359 public void testStaticMethods() throws Exception {
360 final Instrumentation instrumentation = getInstrumentation();
361
362 final ClassFileTransformerFactory transformerFactory =
363 new ASMTransformerFactory(MyAdvice.class);
364
365 m_pointCutRegistryStubFactory.addMethod(
366 A2.class, A2.class.getDeclaredMethod("m3"), "loc1");
367
368 final ClassFileTransformer transformer =
369 transformerFactory.create(m_pointCutRegistry);
370
371 instrumentation.addTransformer(transformer, true);
372 instrumentation.retransformClasses(new Class[] { A2.class, });
373
374 assertEquals(3, A2.m3());
375
376 s_callRecorder.assertSuccess("enter", A2.class, "loc1");
377 s_callRecorder.assertSuccess("exit", A2.class, "loc1", true);
378 s_callRecorder.assertNoMoreCalls();
379
380 instrumentation.removeTransformer(transformer);
381 }
382
383 public void testVariedByteCode() throws Exception {
384 final Instrumentation instrumentation = getInstrumentation();
385
386 final ClassFileTransformerFactory transformerFactory =
387 new ASMTransformerFactory(MyAdvice.class);
388
389 m_pointCutRegistryStubFactory.addConstructor(
390 A5.class, A5.class.getDeclaredConstructor(Integer.TYPE), "loc1");
391
392 m_pointCutRegistryStubFactory.addMethod(
393 A5.class, A5.class.getDeclaredMethod("m1", Integer.TYPE), "loc3");
394 m_pointCutRegistryStubFactory.addMethod(
395 A5.class, A5.class.getDeclaredMethod("m1", String.class), "loc4");
396 m_pointCutRegistryStubFactory.addMethod(
397 A5.class, A5.class.getDeclaredMethod("m2"), "loc5");
398 m_pointCutRegistryStubFactory.addMethod(
399 A5.class, A5.class.getDeclaredMethod("m3"), "loc6");
400
401 final ClassFileTransformer transformer =
402 transformerFactory.create(m_pointCutRegistry);
403
404 instrumentation.addTransformer(transformer, true);
405 instrumentation.retransformClasses(new Class[] { A5.class, });
406
407 final A5 a = new A5(10);
408
409 s_callRecorder.assertSuccess("enter", A5.class, "loc1");
410 s_callRecorder.assertSuccess("exit", A5.class, "loc1", true);
411 s_callRecorder.assertNoMoreCalls();
412
413 assertEquals(-11d, a.m1(1), 0.01d);
414
415 s_callRecorder.assertSuccess("enter", a, "loc3");
416 s_callRecorder.assertSuccess("enter", a, "loc4");
417 s_callRecorder.assertSuccess("exit", a, "loc4", true);
418 s_callRecorder.assertSuccess("exit", a, "loc3", true);
419 s_callRecorder.assertNoMoreCalls();
420
421 instrumentation.removeTransformer(transformer);
422 }
423
424 public void testTargetSource() throws Exception {
425 final Instrumentation instrumentation = getInstrumentation();
426
427 final ClassFileTransformerFactory transformerFactory =
428 new ASMTransformerFactory(MyAdvice.class);
429
430 m_pointCutRegistryStubFactory.addMethod(
431 A4.class,
432 A4.class.getDeclaredMethod("m1", String.class),
433 "X",
434 TargetSource.SECOND_PARAMETER);
435
436 final ClassFileTransformer transformer =
437 transformerFactory.create(m_pointCutRegistry);
438
439 instrumentation.addTransformer(transformer, true);
440 instrumentation.retransformClasses(new Class[] { A4.class, });
441
442 final A4 a = new A4();
443
444 a.m1("Hello");
445
446 s_callRecorder.assertSuccess("enter", "Hello", "X");
447 s_callRecorder.assertSuccess("exit", "Hello", "X", true);
448 s_callRecorder.assertNoMoreCalls();
449
450
451 m_pointCutRegistryStubFactory.addMethod(
452 A4.class,
453 A4.class.getDeclaredMethod("m1", String.class),
454 "Y",
455 TargetSource.FIRST_PARAMETER);
456
457 instrumentation.retransformClasses(new Class[] { A4.class, });
458
459 a.m1("Goodbye");
460
461 s_callRecorder.assertSuccess("enter", "Goodbye", "X");
462 s_callRecorder.assertSuccess("enter", a, "Y");
463 s_callRecorder.assertSuccess("exit", a, "Y", true);
464 s_callRecorder.assertSuccess("exit", "Goodbye", "X", true);
465 s_callRecorder.assertNoMoreCalls();
466
467 instrumentation.removeTransformer(transformer);
468 }
469
470 private static final byte[] serialize(final Object a) throws IOException {
471 final ByteArrayOutputStream byteOutputStream =
472 new ByteArrayOutputStream();
473
474 final ObjectOutputStream objectOutputStream =
475 new ObjectOutputStream(byteOutputStream);
476
477 objectOutputStream.writeObject(a);
478 objectOutputStream.close();
479
480 return byteOutputStream.toByteArray();
481 }
482
483 public static final class A {
484 public int m1() {
485 return 1;
486 }
487
488 private void m2() {
489 throw new RuntimeException("Test");
490 }
491
492 public static int m3() {
493 return 2;
494 }
495
496 public void m4() {
497 m2();
498 }
499 }
500
501 public static final class A2 {
502 public A2(int x) {
503 }
504
505 protected int m1() {
506 return 1;
507 }
508
509 public void m2() {
510 }
511
512 public static int m3() {
513 return 3;
514 }
515 }
516
517 public static final class A3 {
518 }
519
520 public static final class A4 {
521 private A4() {
522 }
523
524 protected A4(String a) {
525 this();
526 }
527
528 public void m1(int a) {
529 m1(Integer.toString(a));
530 }
531
532 private void m1(String a) {
533 }
534 }
535
536 public static final class A5 {
537 private float m_y;
538
539 private A5(int x) {
540 ++x;
541
542 switch (x) {
543 case 99:
544 m_y = x;
545 break;
546 default:
547 m_y = -x;
548 }
549 }
550
551 public double m1(int a) {
552 final int[][] x = new int[3][2];
553
554
555
556 switch (a) {
557 case 1: return m1(Integer.toString(a));
558 case 2: return 1;
559 case 30: return 1;
560 case 4: return 1;
561 case -1: return 1;
562 default: return x[0][0];
563 }
564 }
565
566 private float m1(String a) {
567 return m_y;
568 }
569
570 long m2() {
571 return 2;
572 }
573
574 Object m3() {
575 return this;
576 }
577 }
578
579 public static final class SerializableA implements Serializable {
580 public int m1() {
581 return 1;
582 }
583 }
584
585 public static final class MyAdvice {
586 private static final Method ENTER_METHOD;
587 private static final Method EXIT_METHOD;
588
589 static {
590 try {
591 ENTER_METHOD = MyAdvice.class.getMethod(
592 "enter", Object.class, String.class);
593 EXIT_METHOD = MyAdvice.class.getMethod(
594 "exit", Object.class, String.class, Boolean.TYPE);
595 }
596 catch (Exception e) {
597 throw new ExceptionInInitializerError(e);
598 }
599 }
600
601 public static void enter(Object reference, String location) {
602 s_callRecorder.record(new CallData(ENTER_METHOD,
603 null,
604 reference,
605 location));
606 }
607
608 public static void exit(Object reference,
609 String location,
610 boolean success) {
611
612 s_callRecorder.record(new CallData(EXIT_METHOD,
613 null,
614 reference,
615 location,
616 success));
617 }
618 }
619
620 public static final class BadAdvice1 {
621 }
622
623 public static final class BadAdvice2 {
624 public static void enter(Object reference, String location) { }
625
626 public static void exit(Object reference, String location) { }
627 }
628
629 public static final class BadAdvice3 {
630 public void enter(Object reference, String location) { }
631
632 public static void exit(Object reference,
633 String location,
634 boolean success) { }
635 }
636
637 public static final class BadAdvice4 {
638 public static void enter(Object reference, String location) { }
639
640 public void exit(Object reference, String location, boolean success) { }
641 }
642
643 public static final class PointCutRegistryStubFactory
644 extends RandomStubFactory<PointCutRegistry> {
645
646 private final Map<String, Map<Constructor<?>, List<WeavingDetails>>>
647 m_constructors =
648 new HashMap<String, Map<Constructor<?>, List<WeavingDetails>>>();
649
650 private final Map<String, Map<Method, List<WeavingDetails>>> m_methods =
651 new HashMap<String, Map<Method, List<WeavingDetails>>>();
652
653 protected PointCutRegistryStubFactory() {
654 super(PointCutRegistry.class);
655 }
656
657 public void clear() {
658 m_constructors.clear();
659 m_methods.clear();
660 }
661
662 public Map<Constructor<?>, List<WeavingDetails>>
663 override_getConstructorPointCutsForClass(Object stub, String className) {
664 return m_constructors.get(className);
665 }
666
667 public Map<Method, List<WeavingDetails>>
668 override_getMethodPointCutsForClass(Object stub, String className) {
669 return m_methods.get(className);
670 }
671
672 public void addConstructor(Class<?> theClass,
673 Constructor<?> constructor,
674 String location) {
675 addMember(theClass, constructor, location, m_constructors, null);
676 }
677
678
679
680
681 public void addMethod(Class<?> theClass,
682 String methodName,
683 String location)
684 throws SecurityException, NoSuchMethodException {
685
686 addMethod(theClass,
687 theClass.getDeclaredMethod(methodName),
688 location);
689 }
690
691 public void addMethod(Class<?> theClass,
692 Method method,
693 String location) {
694 addMethod(theClass, method, location, null);
695 }
696
697 public void addMethod(Class<?> theClass,
698 Method method,
699 String location,
700 TargetSource source) {
701 addMember(theClass, method, location, m_methods, source);
702 }
703
704 public <T extends Member> void addMember(
705 Class<?> theClass,
706 T member,
707 String location,
708 Map<String, Map<T, List<WeavingDetails>>> members,
709 TargetSource source) {
710
711 final String internalClassName = theClass.getName().replace('.', '/');
712
713 final Map<T, List<WeavingDetails>> forClass;
714
715 final Map<T, List<WeavingDetails>> existing =
716 members.get(internalClassName);
717
718 if (existing != null) {
719 forClass = existing;
720 }
721 else {
722 forClass = new HashMap<T, List<WeavingDetails>>();
723 members.put(internalClassName, forClass);
724 }
725
726 if (source == null) {
727
728 if (Modifier.isStatic(member.getModifiers()) ||
729 member instanceof Constructor<?>) {
730 source = TargetSource.CLASS;
731 }
732 else {
733 source = TargetSource.FIRST_PARAMETER;
734 }
735 }
736
737 getList(forClass, member).add(new WeavingDetails(location, source));
738 }
739 }
740
741 private static <K, V> List<V> getList(Map<K, List<V>> map, K key) {
742
743 final List<V> existing = map.get(key);
744
745 if (existing == null) {
746 final List<V> list = new ArrayList<V>();
747 map.put(key, list);
748 return list;
749 }
750 else {
751 return existing;
752 }
753 }
754 }