diff options
author | 2017-08-23 12:54:53 -0700 | |
---|---|---|
committer | 2017-09-01 10:10:14 -0700 | |
commit | 798eab03120f6189e8f6aa804d67af1b1d9f00b0 (patch) | |
tree | 828a9a896619a8575bcf20a078f193df54354899 /test/1928-exception-event-exception/src | |
parent | 9c4feaa082d8e8c00611a0657c3f80b1c1179a6f (diff) |
JVMTI Exception and ExceptionCatch events
Add support for the JVMTI can_generate_exception_events capability.
This includes the Exception and ExceptionCatch events and all their
associated behaviors.
Test: ./test.py --host -j50
Bug: 62821960
Bug: 65049545
Change-Id: I21cc8522c01033cdeb47bf34fa433bf04bf7ca5c
Diffstat (limited to 'test/1928-exception-event-exception/src')
6 files changed, 569 insertions, 0 deletions
diff --git a/test/1928-exception-event-exception/src/Main.java b/test/1928-exception-event-exception/src/Main.java new file mode 100644 index 0000000000..11cc773041 --- /dev/null +++ b/test/1928-exception-event-exception/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 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. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1928.run(); + } +} diff --git a/test/1928-exception-event-exception/src/art/Breakpoint.java b/test/1928-exception-event-exception/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1928-exception-event-exception/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017 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.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1928-exception-event-exception/src/art/Exceptions.java b/test/1928-exception-event-exception/src/art/Exceptions.java new file mode 100644 index 0000000000..2c959ec83f --- /dev/null +++ b/test/1928-exception-event-exception/src/art/Exceptions.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 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.Field; +import java.lang.reflect.Method; + +public class Exceptions { + public static native void setupExceptionTracing( + Class<?> methodClass, + Class<?> exceptionClass, + Method exceptionEventMethod, + Method exceptionCaughtEventMethod); + + public static native void enableExceptionCatchEvent(Thread thr); + public static native void enableExceptionEvent(Thread thr); + public static native void disableExceptionCatchEvent(Thread thr); + public static native void disableExceptionEvent(Thread thr); +} diff --git a/test/1928-exception-event-exception/src/art/StackTrace.java b/test/1928-exception-event-exception/src/art/StackTrace.java new file mode 100644 index 0000000000..b12c3df66b --- /dev/null +++ b/test/1928-exception-event-exception/src/art/StackTrace.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017 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.Field; +import java.lang.reflect.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1928-exception-event-exception/src/art/Suspension.java b/test/1928-exception-event-exception/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1928-exception-event-exception/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 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; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1928-exception-event-exception/src/art/Test1928.java b/test/1928-exception-event-exception/src/art/Test1928.java new file mode 100644 index 0000000000..aec88a4079 --- /dev/null +++ b/test/1928-exception-event-exception/src/art/Test1928.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2017 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.util.Arrays; +import java.util.Objects; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; + +public class Test1928 { + public static boolean PRINT_FULL_EXCEPTION = true; + public static ExceptionHandler HANDLER = null; + + public static interface ExceptionHandler { + public void exceptionOccurred( + Executable m, long loc, Throwable exception, Executable catch_m, long catch_l); + } + + private static void PrintStack() { + System.out.println("\tCurrent Stack:"); + for (StackTrace.StackFrameData e : StackTrace.GetStackTrace(Thread.currentThread())) { + if (Objects.equals(e.method.getDeclaringClass().getPackage(), Test1928.class.getPackage())) { + System.out.println("\t\t" + e.method + " @ line = " + + Breakpoint.locationToLine(e.method, e.current_location)); + } + } + } + + public static void ExceptionEvent(Thread thr, + Executable throw_method, + long throw_location, + Throwable exception, + Executable catch_method, + long catch_location) { + System.out.println(thr.getName() + ": " + throw_method + " @ line = " + + Breakpoint.locationToLine(throw_method, throw_location) + " throws " + + exception.getClass() + ": " + exception.getMessage()); + String catch_message; + if (catch_method == null) { + catch_message = "<UNKNOWN>"; + } else { + catch_message = catch_method.toString() + " @ line = " + + Breakpoint.locationToLine(catch_method, catch_location); + } + PrintStack(); + System.out.println("\tWill be caught by: " + catch_message); + if (PRINT_FULL_EXCEPTION) { + System.out.print("exception is: "); + exception.printStackTrace(System.out); + } + if (HANDLER != null) { + HANDLER.exceptionOccurred( + throw_method, throw_location, exception, catch_method, catch_location); + } + } + + public static class BaseTestException extends Error { + public BaseTestException(String e) { super(e); } + public BaseTestException(String e, Throwable t) { super(e, t); } + } + public static class TestException extends BaseTestException { + public TestException(String e) { super(e); } + public TestException(String e, Throwable t) { super(e, t); } + } + + public static class TestExceptionNoRethrow extends TestException { + public TestExceptionNoRethrow(String e) { super(e); } + public TestExceptionNoRethrow(String e, Throwable t) { super(e, t); } + } + + public static class DoNothingHandler implements ExceptionHandler { + public void exceptionOccurred( + Executable m, long loc, Throwable exception, Executable catch_m, long catch_l) { + System.out.println("\tDoing nothing!"); + return; + } + } + + public static class ThrowCatchBase implements ExceptionHandler { + public void exceptionOccurred( + Executable m, long loc, Throwable exception, Executable catch_m, long catch_l) { + System.out.println("\tThrowing BaseTestException and catching it!"); + try { + throw new BaseTestException("ThrowBaseHandler during throw from " + m + " @ line = " + + Breakpoint.locationToLine(m, loc), exception); + } catch (BaseTestException t) { + System.out.println("Caught " + t.getClass().getName() + ": \"" + t.getMessage() + "\""); + if (PRINT_FULL_EXCEPTION) { + t.printStackTrace(System.out); + } + } + } + } + + public static void doThrow() { + throw new TestException("doThrow"); + } + + public static class DoThrowClass implements Runnable { + public void run() { doThrow(); } + } + + public static void throwCatchBaseTestException() { + try { + throw new TestException("throwCatchBaseTestException"); + } catch (BaseTestException t) { + System.out.println("Caught " + t.getClass().getName() + ": \"" + t.getMessage() + "\""); + if (PRINT_FULL_EXCEPTION) { + t.printStackTrace(System.out); + } + } + } + + public static class DoThrowCatchBaseTestException implements Runnable { + public void run() { throwCatchBaseTestException(); } + } + + public static void throwCatchTestException() { + try { + throw new TestException("throwCatchTestException"); + } catch (TestException t) { + System.out.println("Caught " + t.getClass().getName() + ": \"" + t.getMessage() + "\""); + if (PRINT_FULL_EXCEPTION) { + t.printStackTrace(System.out); + } + } + } + + public static class DoThrowCatchTestException implements Runnable { + public void run() { throwCatchTestException(); } + } + + public static void throwCatchTestExceptionNoRethrow() { + try { + throw new TestException("throwCatchTestExceptionNoRethrow"); + } catch (TestExceptionNoRethrow t) { + System.out.println("Caught " + t.getClass().getName() + ": \"" + t.getMessage() + "\""); + if (PRINT_FULL_EXCEPTION) { + t.printStackTrace(System.out); + } + } + } + + public static class DoThrowCatchTestExceptionNoRethrow implements Runnable { + public void run() { throwCatchTestExceptionNoRethrow(); } + } + + public static void run() throws Exception { + // Set up + Exceptions.setupExceptionTracing( + Test1928.class, + TestException.class, + Test1928.class.getDeclaredMethod( + "ExceptionEvent", + Thread.class, + Executable.class, + Long.TYPE, + Throwable.class, + Executable.class, + Long.TYPE), + null); + Exceptions.enableExceptionEvent(Thread.currentThread()); + + ExceptionHandler[] handlers = new ExceptionHandler[] { + new DoNothingHandler(), + new ThrowCatchBase(), + }; + + Runnable[] tests = new Runnable[] { + new DoThrowClass(), + new DoThrowCatchBaseTestException(), + new DoThrowCatchTestException(), + new DoThrowCatchTestExceptionNoRethrow(), + }; + + for (ExceptionHandler handler : handlers) { + for (Runnable test : tests) { + try { + HANDLER = handler; + System.out.printf("Test \"%s\": Running with handler \"%s\"\n", + test.getClass().getName(), handler.getClass().getName()); + test.run(); + System.out.printf("Test \"%s\": No error caught with handler \"%s\"\n", + test.getClass().getName(), handler.getClass().getName()); + } catch (Throwable e) { + System.out.printf("Test \"%s\": Caught error %s:\"%s\" with handler \"%s\"\n", + test.getClass().getName(), + e.getClass().getName(), + e.getMessage(), + handler.getClass().getName()); + if (PRINT_FULL_EXCEPTION) { + e.printStackTrace(System.out); + } + } + System.out.printf("Test \"%s\": Finished running with handler \"%s\"\n", + test.getClass().getName(), handler.getClass().getName()); + HANDLER = null; + } + } + Exceptions.disableExceptionEvent(Thread.currentThread()); + } +} |