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
17package art;
18
19import java.lang.reflect.Method;
20import java.util.Set;
21import java.util.HashSet;
22
23public 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.
57  private static void disableTraceForRI() {
58    if (!System.getProperty("java.vm.name").equals("Dalvik")) {
59      Trace.disableTracing(Thread.currentThread());
60    }
61  }
62
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 {
74    public void methodEntry(Object m);
75    public void methodExited(Object m, boolean exception, Object result);
76    public default Class<?> entryException() { return null; }
77    public default Class<?> exitException() { return null; }
78  }
79
80  public static class NormalTracer implements MethodTracer {
81    public void methodEntry(Object m) {
82      if (testMethods.contains(m)) {
83        System.out.println("Normal: Entering " + m);
84      }
85    }
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 {
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    }
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    }
105    public Class<?> entryException() { return ErrorB.class; }
106  }
107
108  public static class ThrowExitTracer implements MethodTracer {
109    public void methodEntry(Object m) {
110      if (testMethods.contains(m)) {
111        System.out.println("ThrowExit: Entering " + m);
112      }
113    }
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    }
123    public Class<?> exitException() { return ErrorB.class; }
124  }
125
126  public static class ThrowBothTracer implements MethodTracer {
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    }
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    }
142    public Class<?> entryException() { return ErrorB.class; }
143    public Class<?> exitException() { return ErrorC.class; }
144  }
145
146  public static class ForceGCTracer implements MethodTracer {
147    public void methodEntry(Object m) {
148      if (System.getProperty("java.vm.name").equals("Dalvik")) {
149        System.gc();
150      }
151    }
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
159  private static void maybeDisableTracing() throws Exception {
160    if (DISABLE_TRACING) {
161      Trace.disableTracing(Thread.currentThread());
162    }
163  }
164
165  public static void baseNotifyMethodEntry(Object o) {
166    currentTracer.methodEntry(o);
167  }
168  public static void baseNotifyMethodExit(Object o, boolean exception, Object res) {
169    currentTracer.methodExited(o, exception, res);
170  }
171
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  }
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
188  private static String testDescription(MethodTracer type, Runnable test) {
189    return "test[" + type.getClass() + ", " + test.getClass() + "]";
190  }
191
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
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 {
239    public default Class<?> expectedThrow() {
240      return null;
241    }
242  }
243
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 {
281    public void run() {
282      throwA();
283    }
284    @Override
285    public Class<?> expectedThrow() {
286      return ErrorA.class;
287    }
288  }
289
290  private static final class throwANativeClass implements MyRunnable {
291    public void run() {
292      throwANative();
293    }
294    @Override
295    public Class<?> expectedThrow() {
296      return ErrorA.class;
297    }
298  }
299
300  private static final class tryCatchExitClass implements MyRunnable {
301    public void run() {
302      tryCatchExit();
303    }
304  }
305
306  private static final class doNothingClass implements MyRunnable {
307    public void run() {
308      doNothing();
309    }
310  }
311
312  private static final class doNothingNativeClass implements MyRunnable {
313    public void run() {
314      doNothingNative();
315    }
316  }
317
318  private static final class acceptValueClass implements MyRunnable {
319    public void run() {
320      acceptValue(mkTestObject());
321    }
322  }
323
324  private static final class acceptValueNativeClass implements MyRunnable {
325    public void run() {
326      acceptValueNative(mkTestObject());
327    }
328  }
329
330  private static final class returnValueClass implements MyRunnable {
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 {
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 {
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 {
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 {
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 {
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;
374    public ErrorA(String s) { super(s); }
375  }
376
377  private static class ErrorB extends Error {
378    private static final long serialVersionUID = 1;
379    public ErrorB(String s) { super(s); }
380  }
381
382  private static class ErrorC extends Error {
383    private static final long serialVersionUID = 2;
384    public ErrorC(String s) { super(s); }
385  }
386
387  // Does nothing.
388  public static void doNothing() { }
389
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
403  public static float returnFloat() {
404    return doGetFloat();
405  }
406
407  public static double returnDouble() {
408    return doGetDouble();
409  }
410
411  // Throws an ErrorA.
412  public static void throwA() {
413    doThrowA();
414  }
415
416  public static void doThrowA() {
417    throw new ErrorA("Throwing Error A");
418  }
419
420  static final class TestObject {
421    private int idx;
422    public TestObject(int v) {
423      this.idx = v;
424    }
425    @Override
426    public String toString() {
427      return "TestObject(" + idx + ")";
428    }
429  }
430
431  static int counter = 0;
432  public static Object mkTestObject() {
433    return new TestObject(counter++);
434  }
435
436  public static void printObject(Object o) {
437    System.out.println("Recieved " + o);
438  }
439
440  // Returns a newly allocated value.
441  public static Object returnValue() {
442    return mkTestObject();
443  }
444
445  public static void acceptValue(Object o) {
446    printObject(o);
447  }
448
449  public static float doGetFloat() {
450    return 1.618f;
451  }
452
453  public static double doGetDouble() {
454    return 3.14159628;
455  }
456
457  // Calls mkTestObject from native code and returns it.
458  public static native Object returnValueNative();
459  // Calls printObject from native code.
460  public static native void acceptValueNative(Object t);
461  public static native void doNothingNative();
462  public static native void throwANative();
463  public static native float returnFloatNative();
464  public static native double returnDoubleNative();
465}
466