/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package art; import java.lang.reflect.Method; import java.util.Set; import java.util.HashSet; public class Test989 { static boolean PRINT_STACK_TRACE = false; static Set testMethods = new HashSet<>(); static MethodTracer currentTracer = new MethodTracer() { public void methodEntry(Object o) { return; } public void methodExited(Object o, boolean e, Object r) { return; } }; private static boolean DISABLE_TRACING = false; static { try { testMethods.add(Test989.class.getDeclaredMethod("doNothing")); testMethods.add(Test989.class.getDeclaredMethod("doNothingNative")); testMethods.add(Test989.class.getDeclaredMethod("throwA")); testMethods.add(Test989.class.getDeclaredMethod("throwANative")); testMethods.add(Test989.class.getDeclaredMethod("returnFloat")); testMethods.add(Test989.class.getDeclaredMethod("returnFloatNative")); testMethods.add(Test989.class.getDeclaredMethod("returnDouble")); testMethods.add(Test989.class.getDeclaredMethod("returnDoubleNative")); testMethods.add(Test989.class.getDeclaredMethod("returnValue")); testMethods.add(Test989.class.getDeclaredMethod("returnValueNative")); testMethods.add(Test989.class.getDeclaredMethod("acceptValue", Object.class)); testMethods.add(Test989.class.getDeclaredMethod("acceptValueNative", Object.class)); testMethods.add(Test989.class.getDeclaredMethod("tryCatchExit")); } catch (Exception e) { throw new Error("Bad static!", e); } } // Disables tracing only on RI. Used to work around an annoying piece of behavior where in the // RI throwing an exception in an exit hook causes the exit hook to be re-executed. This leads // to an infinite loop on the RI. private static void disableTraceForRI() { if (!System.getProperty("java.vm.name").equals("Dalvik")) { Trace.disableTracing(Thread.currentThread()); } } private static String getInfo(Object m, boolean exception, Object result) { String out = m.toString() + " returned "; if (exception) { out += ""; } else { out += result; } return out; } public static interface MethodTracer { public void methodEntry(Object m); public void methodExited(Object m, boolean exception, Object result); public default Class entryException() { return null; } public default Class exitException() { return null; } } public static class NormalTracer implements MethodTracer { public void methodEntry(Object m) { if (testMethods.contains(m)) { System.out.println("Normal: Entering " + m); } } public void methodExited(Object m, boolean exception, Object result) { if (testMethods.contains(m)) { System.out.println("Normal: Leaving " + getInfo(m, exception, result)); } } } public static class ThrowEnterTracer implements MethodTracer { public void methodEntry(Object m) { if (testMethods.contains(m)) { System.out.println("ThrowEnter: Entering " + m); throw new ErrorB("Throwing error while entering " + m); } } public void methodExited(Object m, boolean exception, Object result) { if (testMethods.contains(m)) { System.out.println("ThrowEnter: Leaving " + getInfo(m, exception, result)); } } public Class entryException() { return ErrorB.class; } } public static class ThrowExitTracer implements MethodTracer { public void methodEntry(Object m) { if (testMethods.contains(m)) { System.out.println("ThrowExit: Entering " + m); } } public void methodExited(Object m, boolean exception, Object result) { if (testMethods.contains(m)) { // The RI goes into an infinite loop if we throw exceptions in an ExitHook. See // disableTraceForRI for explanation. disableTraceForRI(); System.out.println("ThrowExit: Leaving " + getInfo(m, exception, result)); throw new ErrorB("Throwing error while exit " + getInfo(m, exception, result)); } } public Class exitException() { return ErrorB.class; } } public static class ThrowBothTracer implements MethodTracer { public void methodEntry(Object m) { if (testMethods.contains(m)) { System.out.println("ThrowBoth: Entering " + m); throw new ErrorB("Throwing error while entering " + m); } } public void methodExited(Object m, boolean exception, Object result) { if (testMethods.contains(m)) { // The RI goes into an infinite loop if we throw exceptions in an ExitHook. See // disableTraceForRI for explanation. disableTraceForRI(); System.out.println("ThrowBoth: Leaving " + getInfo(m, exception, result)); throw new ErrorC("Throwing error while exit " + getInfo(m, exception, result)); } } public Class entryException() { return ErrorB.class; } public Class exitException() { return ErrorC.class; } } public static class ForceGCTracer implements MethodTracer { public void methodEntry(Object m) { if (System.getProperty("java.vm.name").equals("Dalvik")) { System.gc(); } } public void methodExited(Object m, boolean exception, Object result) { if (System.getProperty("java.vm.name").equals("Dalvik")) { System.gc(); } } } private static void maybeDisableTracing() throws Exception { if (DISABLE_TRACING) { Trace.disableTracing(Thread.currentThread()); } } public static void baseNotifyMethodEntry(Object o) { currentTracer.methodEntry(o); } public static void baseNotifyMethodExit(Object o, boolean exception, Object res) { currentTracer.methodExited(o, exception, res); } private static void setupTracing() throws Exception { Trace.enableMethodTracing( Test989.class, Test989.class.getDeclaredMethod("baseNotifyMethodEntry", Object.class), Test989.class.getDeclaredMethod( "baseNotifyMethodExit", Object.class, Boolean.TYPE, Object.class), Thread.currentThread()); } private static void setEntry(MethodTracer type) throws Exception { if (DISABLE_TRACING || !System.getProperty("java.vm.name").equals("Dalvik")) { Trace.disableTracing(Thread.currentThread()); setupTracing(); } currentTracer = type; } private static String testDescription(MethodTracer type, Runnable test) { return "test[" + type.getClass() + ", " + test.getClass() + "]"; } private static Class getExpectedError(MethodTracer t, MyRunnable r) { if (t.exitException() != null) { return t.exitException(); } else if (t.entryException() != null) { return t.entryException(); } else { return r.expectedThrow(); } } private static void doTest(MethodTracer type, MyRunnable test) throws Exception { Class expected = getExpectedError(type, test); setEntry(type); try { test.run(); // Disabling method tracing just makes this test somewhat faster. maybeDisableTracing(); if (expected == null) { System.out.println( "Received no exception as expected for " + testDescription(type, test) + "."); return; } } catch (Error t) { // Disabling method tracing just makes this test somewhat faster. maybeDisableTracing(); if (expected == null) { throw new Error("Unexpected error occured: " + t + " for " + testDescription(type, test), t); } else if (!expected.isInstance(t)) { throw new Error("Expected error of type " + expected + " not " + t + " for " + testDescription(type, test), t); } else { System.out.println( "Received expected error for " + testDescription(type, test) + " - " + t); if (PRINT_STACK_TRACE) { t.printStackTrace(); } return; } } System.out.println("Expected an error of type " + expected + " but got no exception for " + testDescription(type, test)); // throw new Error("Expected an error of type " + expected + " but got no exception for " // + testDescription(type, test)); } public static interface MyRunnable extends Runnable { public default Class expectedThrow() { return null; } } public static void run() throws Exception { MyRunnable[] testCases = new MyRunnable[] { new doNothingClass(), new doNothingNativeClass(), new throwAClass(), new throwANativeClass(), new returnValueClass(), new returnValueNativeClass(), new acceptValueClass(), new acceptValueNativeClass(), new tryCatchExitClass(), new returnFloatClass(), new returnFloatNativeClass(), new returnDoubleClass(), new returnDoubleNativeClass(), }; MethodTracer[] tracers = new MethodTracer[] { new NormalTracer(), new ThrowEnterTracer(), new ThrowExitTracer(), new ThrowBothTracer(), new ForceGCTracer(), }; setupTracing(); for (MethodTracer t : tracers) { for (MyRunnable r : testCases) { doTest(t, r); } } maybeDisableTracing(); System.out.println("Finished!"); Trace.disableTracing(Thread.currentThread()); } private static final class throwAClass implements MyRunnable { public void run() { throwA(); } @Override public Class expectedThrow() { return ErrorA.class; } } private static final class throwANativeClass implements MyRunnable { public void run() { throwANative(); } @Override public Class expectedThrow() { return ErrorA.class; } } private static final class tryCatchExitClass implements MyRunnable { public void run() { tryCatchExit(); } } private static final class doNothingClass implements MyRunnable { public void run() { doNothing(); } } private static final class doNothingNativeClass implements MyRunnable { public void run() { doNothingNative(); } } private static final class acceptValueClass implements MyRunnable { public void run() { acceptValue(mkTestObject()); } } private static final class acceptValueNativeClass implements MyRunnable { public void run() { acceptValueNative(mkTestObject()); } } private static final class returnValueClass implements MyRunnable { public void run() { Object o = returnValue(); System.out.println("returnValue returned: " + o); } } private static final class returnValueNativeClass implements MyRunnable { public void run() { Object o = returnValueNative(); System.out.println("returnValueNative returned: " + o); } } private static final class returnFloatClass implements MyRunnable { public void run() { float d = returnFloat(); System.out.println("returnFloat returned: " + d); } } private static final class returnFloatNativeClass implements MyRunnable { public void run() { float d = returnFloatNative(); System.out.println("returnFloatNative returned: " + d); } } private static final class returnDoubleClass implements MyRunnable { public void run() { double d = returnDouble(); System.out.println("returnDouble returned: " + d); } } private static final class returnDoubleNativeClass implements MyRunnable { public void run() { double d = returnDoubleNative(); System.out.println("returnDoubleNative returned: " + d); } } private static class ErrorA extends Error { private static final long serialVersionUID = 0; public ErrorA(String s) { super(s); } } private static class ErrorB extends Error { private static final long serialVersionUID = 1; public ErrorB(String s) { super(s); } } private static class ErrorC extends Error { private static final long serialVersionUID = 2; public ErrorC(String s) { super(s); } } // Does nothing. public static void doNothing() { } public static void tryCatchExit() { try { Object o = mkTestObject(); return; } catch (ErrorB b) { System.out.println("ERROR: Caught " + b); b.printStackTrace(); } catch (ErrorC c) { System.out.println("ERROR: Caught " + c); c.printStackTrace(); } } public static float returnFloat() { return doGetFloat(); } public static double returnDouble() { return doGetDouble(); } // Throws an ErrorA. public static void throwA() { doThrowA(); } public static void doThrowA() { throw new ErrorA("Throwing Error A"); } static final class TestObject { private int idx; public TestObject(int v) { this.idx = v; } @Override public String toString() { return "TestObject(" + idx + ")"; } } static int counter = 0; public static Object mkTestObject() { return new TestObject(counter++); } public static void printObject(Object o) { System.out.println("Recieved " + o); } // Returns a newly allocated value. public static Object returnValue() { return mkTestObject(); } public static void acceptValue(Object o) { printObject(o); } public static float doGetFloat() { return 1.618f; } public static double doGetDouble() { return 3.14159628; } // Calls mkTestObject from native code and returns it. public static native Object returnValueNative(); // Calls printObject from native code. public static native void acceptValueNative(Object t); public static native void doNothingNative(); public static native void throwANative(); public static native float returnFloatNative(); public static native double returnDoubleNative(); }