1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package art;
18 
19 import java.lang.reflect.Method;
20 import java.util.Set;
21 import java.util.HashSet;
22 
23 public class Test989 {
24   static boolean PRINT_STACK_TRACE = false;
25   static Set<Method> testMethods = new HashSet<>();
26 
27   static MethodTracer currentTracer = new MethodTracer() {
28     public void methodEntry(Object o) { return; }
29     public void methodExited(Object o, boolean e, Object r) { return; }
30   };
31 
32   private static boolean DISABLE_TRACING = false;
33 
34   static {
35     try {
36       testMethods.add(Test989.class.getDeclaredMethod("doNothing"));
37       testMethods.add(Test989.class.getDeclaredMethod("doNothingNative"));
38       testMethods.add(Test989.class.getDeclaredMethod("throwA"));
39       testMethods.add(Test989.class.getDeclaredMethod("throwANative"));
40       testMethods.add(Test989.class.getDeclaredMethod("returnFloat"));
41       testMethods.add(Test989.class.getDeclaredMethod("returnFloatNative"));
42       testMethods.add(Test989.class.getDeclaredMethod("returnDouble"));
43       testMethods.add(Test989.class.getDeclaredMethod("returnDoubleNative"));
44       testMethods.add(Test989.class.getDeclaredMethod("returnValue"));
45       testMethods.add(Test989.class.getDeclaredMethod("returnValueNative"));
46       testMethods.add(Test989.class.getDeclaredMethod("acceptValue", Object.class));
47       testMethods.add(Test989.class.getDeclaredMethod("acceptValueNative", Object.class));
48       testMethods.add(Test989.class.getDeclaredMethod("tryCatchExit"));
49     } catch (Exception e) {
50       throw new Error("Bad static!", e);
51     }
52   }
53 
54   // Disables tracing only on RI. Used to work around an annoying piece of behavior where in the
55   // RI throwing an exception in an exit hook causes the exit hook to be re-executed. This leads
56   // to an infinite loop on the RI.
disableTraceForRI()57   private static void disableTraceForRI() {
58     if (!System.getProperty("java.vm.name").equals("Dalvik")) {
59       Trace.disableTracing(Thread.currentThread());
60     }
61   }
62 
getInfo(Object m, boolean exception, Object result)63   private static String getInfo(Object m, boolean exception, Object result) {
64     String out = m.toString() + " returned ";
65     if (exception) {
66       out += "<exception>";
67     } else {
68       out += result;
69     }
70     return out;
71   }
72 
73   public static interface MethodTracer {
methodEntry(Object m)74     public void methodEntry(Object m);
methodExited(Object m, boolean exception, Object result)75     public void methodExited(Object m, boolean exception, Object result);
entryException()76     public default Class<?> entryException() { return null; }
exitException()77     public default Class<?> exitException() { return null; }
78   }
79 
80   public static class NormalTracer implements MethodTracer {
methodEntry(Object m)81     public void methodEntry(Object m) {
82       if (testMethods.contains(m)) {
83         System.out.println("Normal: Entering " + m);
84       }
85     }
methodExited(Object m, boolean exception, Object result)86     public void methodExited(Object m, boolean exception, Object result) {
87       if (testMethods.contains(m)) {
88         System.out.println("Normal: Leaving " + getInfo(m, exception, result));
89       }
90     }
91   }
92 
93   public static class ThrowEnterTracer implements MethodTracer {
methodEntry(Object m)94     public void methodEntry(Object m) {
95       if (testMethods.contains(m)) {
96         System.out.println("ThrowEnter: Entering " + m);
97         throw new ErrorB("Throwing error while entering " + m);
98       }
99     }
methodExited(Object m, boolean exception, Object result)100     public void methodExited(Object m, boolean exception, Object result) {
101       if (testMethods.contains(m)) {
102         System.out.println("ThrowEnter: Leaving " + getInfo(m, exception, result));
103       }
104     }
entryException()105     public Class<?> entryException() { return ErrorB.class; }
106   }
107 
108   public static class ThrowExitTracer implements MethodTracer {
methodEntry(Object m)109     public void methodEntry(Object m) {
110       if (testMethods.contains(m)) {
111         System.out.println("ThrowExit: Entering " + m);
112       }
113     }
methodExited(Object m, boolean exception, Object result)114     public void methodExited(Object m, boolean exception, Object result) {
115       if (testMethods.contains(m)) {
116         // The RI goes into an infinite loop if we throw exceptions in an ExitHook. See
117         // disableTraceForRI for explanation.
118         disableTraceForRI();
119         System.out.println("ThrowExit: Leaving " + getInfo(m, exception, result));
120         throw new ErrorB("Throwing error while exit " + getInfo(m, exception, result));
121       }
122     }
exitException()123     public Class<?> exitException() { return ErrorB.class; }
124   }
125 
126   public static class ThrowBothTracer implements MethodTracer {
methodEntry(Object m)127     public void methodEntry(Object m) {
128       if (testMethods.contains(m)) {
129         System.out.println("ThrowBoth: Entering " + m);
130         throw new ErrorB("Throwing error while entering " + m);
131       }
132     }
methodExited(Object m, boolean exception, Object result)133     public void methodExited(Object m, boolean exception, Object result) {
134       if (testMethods.contains(m)) {
135         // The RI goes into an infinite loop if we throw exceptions in an ExitHook. See
136         // disableTraceForRI for explanation.
137         disableTraceForRI();
138         System.out.println("ThrowBoth: Leaving " + getInfo(m, exception, result));
139         throw new ErrorC("Throwing error while exit " + getInfo(m, exception, result));
140       }
141     }
entryException()142     public Class<?> entryException() { return ErrorB.class; }
exitException()143     public Class<?> exitException() { return ErrorC.class; }
144   }
145 
146   public static class ForceGCTracer implements MethodTracer {
methodEntry(Object m)147     public void methodEntry(Object m) {
148       if (System.getProperty("java.vm.name").equals("Dalvik")) {
149         System.gc();
150       }
151     }
methodExited(Object m, boolean exception, Object result)152     public void methodExited(Object m, boolean exception, Object result) {
153       if (System.getProperty("java.vm.name").equals("Dalvik")) {
154         System.gc();
155       }
156     }
157   }
158 
maybeDisableTracing()159   private static void maybeDisableTracing() throws Exception {
160     if (DISABLE_TRACING) {
161       Trace.disableTracing(Thread.currentThread());
162     }
163   }
164 
baseNotifyMethodEntry(Object o)165   public static void baseNotifyMethodEntry(Object o) {
166     currentTracer.methodEntry(o);
167   }
baseNotifyMethodExit(Object o, boolean exception, Object res)168   public static void baseNotifyMethodExit(Object o, boolean exception, Object res) {
169     currentTracer.methodExited(o, exception, res);
170   }
171 
setupTracing()172   private static void setupTracing() throws Exception {
173     Trace.enableMethodTracing(
174         Test989.class,
175         Test989.class.getDeclaredMethod("baseNotifyMethodEntry", Object.class),
176         Test989.class.getDeclaredMethod(
177             "baseNotifyMethodExit", Object.class, Boolean.TYPE, Object.class),
178         Thread.currentThread());
179   }
setEntry(MethodTracer type)180   private static void setEntry(MethodTracer type) throws Exception {
181     if (DISABLE_TRACING || !System.getProperty("java.vm.name").equals("Dalvik")) {
182       Trace.disableTracing(Thread.currentThread());
183       setupTracing();
184     }
185     currentTracer = type;
186   }
187 
testDescription(MethodTracer type, Runnable test)188   private static String testDescription(MethodTracer type, Runnable test) {
189     return "test[" + type.getClass() + ", " + test.getClass() + "]";
190   }
191 
getExpectedError(MethodTracer t, MyRunnable r)192   private static Class<?> getExpectedError(MethodTracer t, MyRunnable r) {
193     if (t.exitException() != null) {
194       return t.exitException();
195     } else if (t.entryException() != null) {
196       return t.entryException();
197     } else {
198       return r.expectedThrow();
199     }
200   }
201 
doTest(MethodTracer type, MyRunnable test)202   private static void doTest(MethodTracer type, MyRunnable test) throws Exception {
203     Class<?> expected = getExpectedError(type, test);
204 
205     setEntry(type);
206     try {
207       test.run();
208       // Disabling method tracing just makes this test somewhat faster.
209       maybeDisableTracing();
210       if (expected == null) {
211         System.out.println(
212             "Received no exception as expected for " + testDescription(type, test) + ".");
213         return;
214       }
215     } catch (Error t) {
216       // Disabling method tracing just makes this test somewhat faster.
217       maybeDisableTracing();
218       if (expected == null) {
219         throw new Error("Unexpected error occured: " + t + " for " + testDescription(type, test), t);
220       } else if (!expected.isInstance(t)) {
221         throw new Error("Expected error of type " + expected + " not " + t +
222             " for " + testDescription(type, test), t);
223       } else {
224         System.out.println(
225             "Received expected error for " + testDescription(type, test) + " - " + t);
226         if (PRINT_STACK_TRACE) {
227           t.printStackTrace();
228         }
229         return;
230       }
231     }
232     System.out.println("Expected an error of type " + expected + " but got no exception for "
233         + testDescription(type, test));
234     // throw new Error("Expected an error of type " + expected + " but got no exception for "
235     //     + testDescription(type, test));
236   }
237 
238   public static interface MyRunnable extends Runnable {
expectedThrow()239     public default Class<?> expectedThrow() {
240       return null;
241     }
242   }
243 
run()244   public static void run() throws Exception {
245     MyRunnable[] testCases = new MyRunnable[] {
246       new doNothingClass(),
247       new doNothingNativeClass(),
248       new throwAClass(),
249       new throwANativeClass(),
250       new returnValueClass(),
251       new returnValueNativeClass(),
252       new acceptValueClass(),
253       new acceptValueNativeClass(),
254       new tryCatchExitClass(),
255       new returnFloatClass(),
256       new returnFloatNativeClass(),
257       new returnDoubleClass(),
258       new returnDoubleNativeClass(),
259     };
260     MethodTracer[] tracers = new MethodTracer[] {
261       new NormalTracer(),
262       new ThrowEnterTracer(),
263       new ThrowExitTracer(),
264       new ThrowBothTracer(),
265       new ForceGCTracer(),
266     };
267 
268     setupTracing();
269     for (MethodTracer t : tracers) {
270       for (MyRunnable r : testCases) {
271         doTest(t, r);
272       }
273     }
274 
275     maybeDisableTracing();
276     System.out.println("Finished!");
277     Trace.disableTracing(Thread.currentThread());
278   }
279 
280   private static final class throwAClass implements MyRunnable {
run()281     public void run() {
282       throwA();
283     }
284     @Override
expectedThrow()285     public Class<?> expectedThrow() {
286       return ErrorA.class;
287     }
288   }
289 
290   private static final class throwANativeClass implements MyRunnable {
run()291     public void run() {
292       throwANative();
293     }
294     @Override
expectedThrow()295     public Class<?> expectedThrow() {
296       return ErrorA.class;
297     }
298   }
299 
300   private static final class tryCatchExitClass implements MyRunnable {
run()301     public void run() {
302       tryCatchExit();
303     }
304   }
305 
306   private static final class doNothingClass implements MyRunnable {
run()307     public void run() {
308       doNothing();
309     }
310   }
311 
312   private static final class doNothingNativeClass implements MyRunnable {
run()313     public void run() {
314       doNothingNative();
315     }
316   }
317 
318   private static final class acceptValueClass implements MyRunnable {
run()319     public void run() {
320       acceptValue(mkTestObject());
321     }
322   }
323 
324   private static final class acceptValueNativeClass implements MyRunnable {
run()325     public void run() {
326       acceptValueNative(mkTestObject());
327     }
328   }
329 
330   private static final class returnValueClass implements MyRunnable {
run()331     public void run() {
332       Object o = returnValue();
333       System.out.println("returnValue returned: " + o);
334     }
335   }
336 
337   private static final class returnValueNativeClass implements MyRunnable {
run()338     public void run() {
339       Object o = returnValueNative();
340       System.out.println("returnValueNative returned: " + o);
341     }
342   }
343 
344   private static final class returnFloatClass implements MyRunnable {
run()345     public void run() {
346       float d = returnFloat();
347       System.out.println("returnFloat returned: " + d);
348     }
349   }
350 
351   private static final class returnFloatNativeClass implements MyRunnable {
run()352     public void run() {
353       float d = returnFloatNative();
354       System.out.println("returnFloatNative returned: " + d);
355     }
356   }
357 
358   private static final class returnDoubleClass implements MyRunnable {
run()359     public void run() {
360       double d = returnDouble();
361       System.out.println("returnDouble returned: " + d);
362     }
363   }
364 
365   private static final class returnDoubleNativeClass implements MyRunnable {
run()366     public void run() {
367       double d = returnDoubleNative();
368       System.out.println("returnDoubleNative returned: " + d);
369     }
370   }
371 
372   private static class ErrorA extends Error {
373     private static final long serialVersionUID = 0;
ErrorA(String s)374     public ErrorA(String s) { super(s); }
375   }
376 
377   private static class ErrorB extends Error {
378     private static final long serialVersionUID = 1;
ErrorB(String s)379     public ErrorB(String s) { super(s); }
380   }
381 
382   private static class ErrorC extends Error {
383     private static final long serialVersionUID = 2;
ErrorC(String s)384     public ErrorC(String s) { super(s); }
385   }
386 
387   // Does nothing.
doNothing()388   public static void doNothing() { }
389 
tryCatchExit()390   public static void tryCatchExit() {
391     try {
392       Object o = mkTestObject();
393       return;
394     } catch (ErrorB b) {
395       System.out.println("ERROR: Caught " + b);
396       b.printStackTrace();
397     } catch (ErrorC c) {
398       System.out.println("ERROR: Caught " + c);
399       c.printStackTrace();
400     }
401   }
402 
returnFloat()403   public static float returnFloat() {
404     return doGetFloat();
405   }
406 
returnDouble()407   public static double returnDouble() {
408     return doGetDouble();
409   }
410 
411   // Throws an ErrorA.
throwA()412   public static void throwA() {
413     doThrowA();
414   }
415 
doThrowA()416   public static void doThrowA() {
417     throw new ErrorA("Throwing Error A");
418   }
419 
420   static final class TestObject {
421     private int idx;
TestObject(int v)422     public TestObject(int v) {
423       this.idx = v;
424     }
425     @Override
toString()426     public String toString() {
427       return "TestObject(" + idx + ")";
428     }
429   }
430 
431   static int counter = 0;
mkTestObject()432   public static Object mkTestObject() {
433     return new TestObject(counter++);
434   }
435 
printObject(Object o)436   public static void printObject(Object o) {
437     System.out.println("Recieved " + o);
438   }
439 
440   // Returns a newly allocated value.
returnValue()441   public static Object returnValue() {
442     return mkTestObject();
443   }
444 
acceptValue(Object o)445   public static void acceptValue(Object o) {
446     printObject(o);
447   }
448 
doGetFloat()449   public static float doGetFloat() {
450     return 1.618f;
451   }
452 
doGetDouble()453   public static double doGetDouble() {
454     return 3.14159628;
455   }
456 
457   // Calls mkTestObject from native code and returns it.
returnValueNative()458   public static native Object returnValueNative();
459   // Calls printObject from native code.
acceptValueNative(Object t)460   public static native void acceptValueNative(Object t);
doNothingNative()461   public static native void doNothingNative();
throwANative()462   public static native void throwANative();
returnFloatNative()463   public static native float returnFloatNative();
returnDoubleNative()464   public static native double returnDoubleNative();
465 }
466