| /* |
| * 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.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.util.Base64; |
| import java.util.EnumSet; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.function.Consumer; |
| |
| import static art.SuspendEvents.setupTest; |
| import static art.SuspendEvents.setupSuspendBreakpointFor; |
| import static art.SuspendEvents.clearSuspendBreakpointFor; |
| import static art.SuspendEvents.setupSuspendSingleStepAt; |
| import static art.SuspendEvents.setupFieldSuspendFor; |
| import static art.SuspendEvents.setupSuspendMethodEvent; |
| import static art.SuspendEvents.setupSuspendExceptionEvent; |
| import static art.SuspendEvents.setupSuspendPopFrameEvent; |
| import static art.SuspendEvents.EVENT_TYPE_CLASS_LOAD; |
| import static art.SuspendEvents.EVENT_TYPE_CLASS_PREPARE; |
| import static art.SuspendEvents.setupSuspendClassEvent; |
| |
| public class Test1953 { |
| private static boolean IS_ART = System.getProperty("java.vm.name").equals("Dalvik"); |
| public final boolean canRunClassLoadTests; |
| public static void doNothing() {} |
| |
| public interface TestRunnable extends Runnable { |
| public int getBaseCallCount(); |
| public Method getCalledMethod() throws Exception; |
| public default Method getCallingMethod() throws Exception { |
| return this.getClass().getMethod("run"); |
| }; |
| } |
| |
| public static interface TestSuspender { |
| public void setup(Thread thr); |
| public void waitForSuspend(Thread thr); |
| public void cleanup(Thread thr); |
| } |
| |
| public static interface ThreadRunnable { public void run(Thread thr); } |
| public static TestSuspender makeSuspend(final ThreadRunnable setup, final ThreadRunnable clean) { |
| return new TestSuspender() { |
| public void setup(Thread thr) { setup.run(thr); } |
| public void waitForSuspend(Thread thr) { SuspendEvents.waitForSuspendHit(thr); } |
| public void cleanup(Thread thr) { clean.run(thr); } |
| }; |
| } |
| |
| public void runTestOn(TestRunnable testObj, ThreadRunnable su, ThreadRunnable cl) throws |
| Exception { |
| runTestOn(testObj, makeSuspend(su, cl)); |
| } |
| |
| private static void SafePrintStackTrace(StackTraceElement st[]) { |
| for (StackTraceElement e : st) { |
| System.out.println("\t" + e.getClassName() + "." + e.getMethodName() + "(" + |
| (e.isNativeMethod() ? "Native Method" : e.getFileName()) + ")"); |
| if (e.getClassName().equals("art.Test1953") && e.getMethodName().equals("runTests")) { |
| System.out.println("\t<Additional frames hidden>"); |
| break; |
| } |
| } |
| } |
| |
| public void runTestOn(TestRunnable testObj, TestSuspender su) throws Exception { |
| System.out.println("Single call with PopFrame on " + testObj + " base-call-count: " + |
| testObj.getBaseCallCount()); |
| final CountDownLatch continue_latch = new CountDownLatch(1); |
| final CountDownLatch startup_latch = new CountDownLatch(1); |
| Runnable await = () -> { |
| try { |
| startup_latch.countDown(); |
| continue_latch.await(); |
| } catch (Exception e) { |
| throw new Error("Failed to await latch", e); |
| } |
| }; |
| Thread thr = new Thread(() -> { await.run(); testObj.run(); }); |
| thr.start(); |
| |
| // Wait until the other thread is started. |
| startup_latch.await(); |
| |
| // Do any final setup. |
| preTest.accept(testObj); |
| |
| // Setup suspension method on the thread. |
| su.setup(thr); |
| |
| // Let the other thread go. |
| continue_latch.countDown(); |
| |
| // Wait for the other thread to hit the breakpoint/watchpoint/whatever and suspend itself |
| // (without re-entering java) |
| su.waitForSuspend(thr); |
| |
| // Cleanup the breakpoint/watchpoint/etc. |
| su.cleanup(thr); |
| |
| try { |
| // Pop the frame. |
| popFrame(thr); |
| } catch (Exception e) { |
| System.out.println("Failed to pop frame due to " + e); |
| SafePrintStackTrace(e.getStackTrace()); |
| } |
| |
| // Start the other thread going again. |
| Suspension.resume(thr); |
| |
| // Wait for the other thread to finish. |
| thr.join(); |
| |
| // See how many times calledFunction was called. |
| System.out.println("result is " + testObj + " base-call count: " + testObj.getBaseCallCount()); |
| } |
| |
| public static abstract class AbstractTestObject implements TestRunnable { |
| public int callerCnt; |
| |
| public AbstractTestObject() { |
| callerCnt = 0; |
| } |
| |
| public int getBaseCallCount() { |
| return callerCnt; |
| } |
| |
| public void run() { |
| callerCnt++; |
| // This function should be re-executed by the popFrame. |
| calledFunction(); |
| } |
| |
| public Method getCalledMethod() throws Exception { |
| return this.getClass().getMethod("calledFunction"); |
| } |
| |
| public abstract void calledFunction(); |
| } |
| |
| public static class RedefineTestObject extends AbstractTestObject implements Runnable { |
| public static enum RedefineState { ORIGINAL, REDEFINED, }; |
| /* public static class RedefineTestObject extends AbstractTestObject implements Runnable { |
| * public static final byte[] CLASS_BYTES; |
| * public static final byte[] DEX_BYTES; |
| * static { |
| * CLASS_BYTES = null; |
| * DEX_BYTES = null; |
| * } |
| * |
| * public EnumSet<RedefineState> redefine_states; |
| * public RedefineTestObject() { |
| * super(); |
| * redefine_states = EnumSet.noneOf(RedefineState.class); |
| * } |
| * public String toString() { |
| * return "RedefineTestObject { states: " + redefine_states.toString() |
| * + " current: REDEFINED }"; |
| * } |
| * public void calledFunction() { |
| * redefine_states.add(RedefineState.REDEFINED); // line +0 |
| * // We will trigger the redefinition using a breakpoint on the next line. |
| * doNothing(); // line +2 |
| * } |
| * } |
| */ |
| public static final byte[] CLASS_BYTES = Base64.getDecoder().decode( |
| "yv66vgAAADUATQoADQAjBwAkCgAlACYJAAwAJwoAJQAoEgAAACwJAAIALQoAJQAuCgAvADAJAAwA" + |
| "MQkADAAyBwAzBwA0BwA2AQASUmVkZWZpbmVUZXN0T2JqZWN0AQAMSW5uZXJDbGFzc2VzAQANUmVk" + |
| "ZWZpbmVTdGF0ZQEAC0NMQVNTX0JZVEVTAQACW0IBAAlERVhfQllURVMBAA9yZWRlZmluZV9zdGF0" + |
| "ZXMBABNMamF2YS91dGlsL0VudW1TZXQ7AQAJU2lnbmF0dXJlAQBETGphdmEvdXRpbC9FbnVtU2V0" + |
| "PExhcnQvVGVzdDE5NTMkUmVkZWZpbmVUZXN0T2JqZWN0JFJlZGVmaW5lU3RhdGU7PjsBAAY8aW5p" + |
| "dD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAIdG9TdHJpbmcBABQoKUxqYXZhL2xh" + |
| "bmcvU3RyaW5nOwEADmNhbGxlZEZ1bmN0aW9uAQAIPGNsaW5pdD4BAApTb3VyY2VGaWxlAQANVGVz" + |
| "dDE5NTMuamF2YQwAGQAaAQAtYXJ0L1Rlc3QxOTUzJFJlZGVmaW5lVGVzdE9iamVjdCRSZWRlZmlu" + |
| "ZVN0YXRlBwA3DAA4ADkMABUAFgwAHQAeAQAQQm9vdHN0cmFwTWV0aG9kcw8GADoIADsMADwAPQwA" + |
| "PgA/DABAAEEHAEIMAEMAGgwAEgATDAAUABMBAB9hcnQvVGVzdDE5NTMkUmVkZWZpbmVUZXN0T2Jq" + |
| "ZWN0AQAfYXJ0L1Rlc3QxOTUzJEFic3RyYWN0VGVzdE9iamVjdAEAEkFic3RyYWN0VGVzdE9iamVj" + |
| "dAEAEmphdmEvbGFuZy9SdW5uYWJsZQEAEWphdmEvdXRpbC9FbnVtU2V0AQAGbm9uZU9mAQAmKExq" + |
| "YXZhL2xhbmcvQ2xhc3M7KUxqYXZhL3V0aWwvRW51bVNldDsKAEQARQEAM1JlZGVmaW5lVGVzdE9i" + |
| "amVjdCB7IHN0YXRlczogASBjdXJyZW50OiBSRURFRklORUQgfQEAF21ha2VDb25jYXRXaXRoQ29u" + |
| "c3RhbnRzAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAAlSRURFRklO" + |
| "RUQBAC9MYXJ0L1Rlc3QxOTUzJFJlZGVmaW5lVGVzdE9iamVjdCRSZWRlZmluZVN0YXRlOwEAA2Fk" + |
| "ZAEAFShMamF2YS9sYW5nL09iamVjdDspWgEADGFydC9UZXN0MTk1MwEACWRvTm90aGluZwcARgwA" + |
| "PABJAQAkamF2YS9sYW5nL2ludm9rZS9TdHJpbmdDb25jYXRGYWN0b3J5BwBLAQAGTG9va3VwAQCY" + |
| "KExqYXZhL2xhbmcvaW52b2tlL01ldGhvZEhhbmRsZXMkTG9va3VwO0xqYXZhL2xhbmcvU3RyaW5n" + |
| "O0xqYXZhL2xhbmcvaW52b2tlL01ldGhvZFR5cGU7TGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xh" + |
| "bmcvT2JqZWN0OylMamF2YS9sYW5nL2ludm9rZS9DYWxsU2l0ZTsHAEwBACVqYXZhL2xhbmcvaW52" + |
| "b2tlL01ldGhvZEhhbmRsZXMkTG9va3VwAQAeamF2YS9sYW5nL2ludm9rZS9NZXRob2RIYW5kbGVz" + |
| "ACEADAANAAEADgADABkAEgATAAAAGQAUABMAAAABABUAFgABABcAAAACABgABAABABkAGgABABsA" + |
| "AAAuAAIAAQAAAA4qtwABKhICuAADtQAEsQAAAAEAHAAAAA4AAwAAACEABAAiAA0AIwABAB0AHgAB" + |
| "ABsAAAAlAAEAAQAAAA0qtAAEtgAFugAGAACwAAAAAQAcAAAABgABAAAAJQABAB8AGgABABsAAAAv" + |
| "AAIAAQAAAA8qtAAEsgAHtgAIV7gACbEAAAABABwAAAAOAAMAAAApAAsAKwAOACwACAAgABoAAQAb" + |
| "AAAAKQABAAAAAAAJAbMACgGzAAuxAAAAAQAcAAAADgADAAAAGwAEABwACAAdAAMAIQAAAAIAIgAQ" + |
| "AAAAIgAEAAwALwAPAAkAAgAMABFAGQANAC8ANQQJAEcASgBIABkAKQAAAAgAAQAqAAEAKw=="); |
| public static final byte[] DEX_BYTES = Base64.getDecoder().decode( |
| "ZGV4CjAzNQAaR23N6WpunLRVX+BexSuzzNNiHNOvQpFoBwAAcAAAAHhWNBIAAAAAAAAAAKQGAAAq" + |
| "AAAAcAAAABEAAAAYAQAABQAAAFwBAAAEAAAAmAEAAAwAAAC4AQAAAQAAABgCAAAwBQAAOAIAACID" + |
| "AAA5AwAAQwMAAEsDAABPAwAAXAMAAGcDAABqAwAAbgMAAJEDAADCAwAA5QMAAPUDAAAZBAAAOQQA" + |
| "AFwEAAB7BAAAjgQAAKIEAAC4BAAAzAQAAOcEAAD8BAAAEQUAABwFAAAwBQAATwUAAF4FAABhBQAA" + |
| "ZAUAAGgFAABsBQAAeQUAAH4FAACGBQAAlgUAAKEFAACnBQAArwUAAMAFAADKBQAA0QUAAAgAAAAJ" + |
| "AAAACgAAAAsAAAAMAAAADQAAAA4AAAAPAAAAEAAAABEAAAASAAAAEwAAABQAAAAVAAAAGwAAABwA" + |
| "AAAeAAAABgAAAAsAAAAAAAAABwAAAAwAAAAMAwAABwAAAA0AAAAUAwAAGwAAAA4AAAAAAAAAHQAA" + |
| "AA8AAAAcAwAAAQABABcAAAACABAABAAAAAIAEAAFAAAAAgANACYAAAAAAAMAAgAAAAIAAwABAAAA" + |
| "AgADAAIAAAACAAMAIgAAAAIAAAAnAAAAAwADACMAAAAMAAMAAgAAAAwAAQAhAAAADAAAACcAAAAN" + |
| "AAQAIAAAAA0AAgAlAAAADQAAACcAAAACAAAAAQAAAAAAAAAEAwAAGgAAAIwGAABRBgAAAAAAAAQA" + |
| "AQACAAAA+gIAAB0AAABUMAMAbhALAAAADAAiAQwAcBAGAAEAGgIZAG4gBwAhAG4gBwABABoAAABu" + |
| "IAcAAQBuEAgAAQAMABEAAAABAAAAAAAAAPQCAAAGAAAAEgBpAAEAaQACAA4AAgABAAEAAADuAgAA" + |
| "DAAAAHAQAAABABwAAQBxEAoAAAAMAFsQAwAOAAMAAQACAAAA/gIAAAsAAABUIAMAYgEAAG4gCQAQ" + |
| "AHEABQAAAA4AIQAOPIcAGwAOPC0AJQAOACkADnk8AAEAAAAKAAAAAQAAAAsAAAABAAAACAAAAAEA" + |
| "AAAJABUgY3VycmVudDogUkVERUZJTkVEIH0ACDxjbGluaXQ+AAY8aW5pdD4AAj47AAtDTEFTU19C" + |
| "WVRFUwAJREVYX0JZVEVTAAFMAAJMTAAhTGFydC9UZXN0MTk1MyRBYnN0cmFjdFRlc3RPYmplY3Q7" + |
| "AC9MYXJ0L1Rlc3QxOTUzJFJlZGVmaW5lVGVzdE9iamVjdCRSZWRlZmluZVN0YXRlOwAhTGFydC9U" + |
| "ZXN0MTk1MyRSZWRlZmluZVRlc3RPYmplY3Q7AA5MYXJ0L1Rlc3QxOTUzOwAiTGRhbHZpay9hbm5v" + |
| "dGF0aW9uL0VuY2xvc2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ACFM" + |
| "ZGFsdmlrL2Fubm90YXRpb24vTWVtYmVyQ2xhc3NlczsAHUxkYWx2aWsvYW5ub3RhdGlvbi9TaWdu" + |
| "YXR1cmU7ABFMamF2YS9sYW5nL0NsYXNzOwASTGphdmEvbGFuZy9PYmplY3Q7ABRMamF2YS9sYW5n" + |
| "L1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7" + |
| "ABNMamF2YS91dGlsL0VudW1TZXQ7ABNMamF2YS91dGlsL0VudW1TZXQ8AAlSRURFRklORUQAElJl" + |
| "ZGVmaW5lVGVzdE9iamVjdAAdUmVkZWZpbmVUZXN0T2JqZWN0IHsgc3RhdGVzOiAADVRlc3QxOTUz" + |
| "LmphdmEAAVYAAVoAAlpMAAJbQgALYWNjZXNzRmxhZ3MAA2FkZAAGYXBwZW5kAA5jYWxsZWRGdW5j" + |
| "dGlvbgAJZG9Ob3RoaW5nAARuYW1lAAZub25lT2YAD3JlZGVmaW5lX3N0YXRlcwAIdG9TdHJpbmcA" + |
| "BXZhbHVlAFt+fkQ4eyJtaW4tYXBpIjoxLCJzaGEtMSI6IjUyNzNjM2RmZWUxMDQ2NzIwYWY0MjVm" + |
| "YTg1NTMxNmM5OWM4NmM4ZDIiLCJ2ZXJzaW9uIjoiMS4zLjE4LWRldiJ9AAIHASgcAxcWFwkXAwIE" + |
| "ASgYAwIFAh8ECSQXGAIGASgcARgBAgECAgEZARkDAQGIgASEBQGBgASgBQMByAUBAbgEAAAAAAAB" + |
| "AAAALgYAAAMAAAA6BgAAQAYAAEkGAAB8BgAAAQAAAAAAAAAAAAAAAwAAAHQGAAAQAAAAAAAAAAEA" + |
| "AAAAAAAAAQAAACoAAABwAAAAAgAAABEAAAAYAQAAAwAAAAUAAABcAQAABAAAAAQAAACYAQAABQAA" + |
| "AAwAAAC4AQAABgAAAAEAAAAYAgAAASAAAAQAAAA4AgAAAyAAAAQAAADuAgAAARAAAAQAAAAEAwAA" + |
| "AiAAACoAAAAiAwAABCAAAAQAAAAuBgAAACAAAAEAAABRBgAAAxAAAAMAAABwBgAABiAAAAEAAACM" + |
| "BgAAABAAAAEAAACkBgAA"); |
| |
| public EnumSet<RedefineState> redefine_states; |
| public RedefineTestObject() { |
| super(); |
| redefine_states = EnumSet.noneOf(RedefineState.class); |
| } |
| |
| public String toString() { |
| return "RedefineTestObject { states: " + redefine_states.toString() + " current: ORIGINAL }"; |
| } |
| |
| public void calledFunction() { |
| redefine_states.add(RedefineState.ORIGINAL); // line +0 |
| // We will trigger the redefinition using a breakpoint on the next line. |
| doNothing(); // line +2 |
| } |
| } |
| |
| public static class ClassLoadObject implements TestRunnable { |
| public int cnt; |
| public int baseCallCnt; |
| |
| public static final String[] CLASS_NAMES = new String[] { |
| "Lart/Test1953$ClassLoadObject$TC0;", |
| "Lart/Test1953$ClassLoadObject$TC1;", |
| "Lart/Test1953$ClassLoadObject$TC2;", |
| "Lart/Test1953$ClassLoadObject$TC3;", |
| "Lart/Test1953$ClassLoadObject$TC4;", |
| "Lart/Test1953$ClassLoadObject$TC5;", |
| "Lart/Test1953$ClassLoadObject$TC6;", |
| "Lart/Test1953$ClassLoadObject$TC7;", |
| "Lart/Test1953$ClassLoadObject$TC8;", |
| "Lart/Test1953$ClassLoadObject$TC9;", |
| }; |
| |
| private static int curClass = 0; |
| |
| private static class TC0 { public static int foo; static { foo = 1; } } |
| private static class TC1 { public static int foo; static { foo = 2; } } |
| private static class TC2 { public static int foo; static { foo = 3; } } |
| private static class TC3 { public static int foo; static { foo = 4; } } |
| private static class TC4 { public static int foo; static { foo = 5; } } |
| private static class TC5 { public static int foo; static { foo = 6; } } |
| private static class TC6 { public static int foo; static { foo = 7; } } |
| private static class TC7 { public static int foo; static { foo = 8; } } |
| private static class TC8 { public static int foo; static { foo = 9; } } |
| private static class TC9 { public static int foo; static { foo = 10; } } |
| |
| public ClassLoadObject() { |
| super(); |
| cnt = 0; |
| baseCallCnt = 0; |
| } |
| |
| public int getBaseCallCount() { |
| return baseCallCnt; |
| } |
| |
| public void run() { |
| baseCallCnt++; |
| if (curClass == 0) { |
| $noprecompile$calledFunction0(); |
| } else if (curClass == 1) { |
| $noprecompile$calledFunction1(); |
| } else if (curClass == 2) { |
| $noprecompile$calledFunction2(); |
| } else if (curClass == 3) { |
| $noprecompile$calledFunction3(); |
| } else if (curClass == 4) { |
| $noprecompile$calledFunction4(); |
| } else if (curClass == 5) { |
| $noprecompile$calledFunction5(); |
| } else if (curClass == 6) { |
| $noprecompile$calledFunction6(); |
| } else if (curClass == 7) { |
| $noprecompile$calledFunction7(); |
| } else if (curClass == 8) { |
| $noprecompile$calledFunction8(); |
| } else if (curClass == 9) { |
| $noprecompile$calledFunction9(); |
| } |
| curClass++; |
| } |
| |
| public Method getCalledMethod() throws Exception { |
| return this.getClass().getMethod("jnoprecompile$calledFunction" + curClass); |
| } |
| |
| // Give these all a tag to prevent 1954 from compiling them (and loading the class as a |
| // consequence). |
| public void $noprecompile$calledFunction0() { |
| cnt++; |
| System.out.println("TC0.foo == " + TC0.foo); |
| } |
| |
| public void $noprecompile$calledFunction1() { |
| cnt++; |
| System.out.println("TC1.foo == " + TC1.foo); |
| } |
| |
| public void $noprecompile$calledFunction2() { |
| cnt++; |
| System.out.println("TC2.foo == " + TC2.foo); |
| } |
| |
| public void $noprecompile$calledFunction3() { |
| cnt++; |
| System.out.println("TC3.foo == " + TC3.foo); |
| } |
| |
| public void $noprecompile$calledFunction4() { |
| cnt++; |
| System.out.println("TC4.foo == " + TC4.foo); |
| } |
| |
| public void $noprecompile$calledFunction5() { |
| cnt++; |
| System.out.println("TC5.foo == " + TC5.foo); |
| } |
| |
| public void $noprecompile$calledFunction6() { |
| cnt++; |
| System.out.println("TC6.foo == " + TC6.foo); |
| } |
| |
| public void $noprecompile$calledFunction7() { |
| cnt++; |
| System.out.println("TC7.foo == " + TC7.foo); |
| } |
| |
| public void $noprecompile$calledFunction8() { |
| cnt++; |
| System.out.println("TC8.foo == " + TC8.foo); |
| } |
| |
| public void $noprecompile$calledFunction9() { |
| cnt++; |
| System.out.println("TC9.foo == " + TC9.foo); |
| } |
| |
| public String toString() { |
| return "ClassLoadObject { cnt: " + cnt + ", curClass: " + curClass + "}"; |
| } |
| } |
| |
| public static class FieldBasedTestObject extends AbstractTestObject implements Runnable { |
| public int cnt; |
| public int TARGET_FIELD; |
| public FieldBasedTestObject() { |
| super(); |
| cnt = 0; |
| TARGET_FIELD = 0; |
| } |
| |
| public void calledFunction() { |
| cnt++; |
| // We put a watchpoint here and PopFrame when we are at it. |
| TARGET_FIELD += 10; |
| if (cnt == 1) { System.out.println("FAILED: No pop on first call!"); } |
| } |
| |
| public String toString() { |
| return "FieldBasedTestObject { cnt: " + cnt + ", TARGET_FIELD: " + TARGET_FIELD + " }"; |
| } |
| } |
| |
| public static class StandardTestObject extends AbstractTestObject implements Runnable { |
| public int cnt; |
| public final boolean check; |
| |
| public StandardTestObject(boolean check) { |
| super(); |
| cnt = 0; |
| this.check = check; |
| } |
| |
| public StandardTestObject() { |
| this(true); |
| } |
| |
| public void calledFunction() { |
| cnt++; // line +0 |
| // We put a breakpoint here and PopFrame when we are at it. |
| doNothing(); // line +2 |
| if (check && cnt == 1) { System.out.println("FAILED: No pop on first call!"); } |
| } |
| |
| public String toString() { |
| return "StandardTestObject { cnt: " + cnt + " }"; |
| } |
| } |
| |
| public static class SynchronizedFunctionTestObject extends AbstractTestObject implements Runnable { |
| public int cnt; |
| |
| public SynchronizedFunctionTestObject() { |
| super(); |
| cnt = 0; |
| } |
| |
| public synchronized void calledFunction() { |
| cnt++; // line +0 |
| // We put a breakpoint here and PopFrame when we are at it. |
| doNothing(); // line +2 |
| } |
| |
| public String toString() { |
| return "SynchronizedFunctionTestObject { cnt: " + cnt + " }"; |
| } |
| } |
| public static class SynchronizedTestObject extends AbstractTestObject implements Runnable { |
| public int cnt; |
| public final Object lock; |
| |
| public SynchronizedTestObject() { |
| this(new Object()); |
| } |
| |
| public SynchronizedTestObject(Object l) { |
| super(); |
| cnt = 0; |
| lock = l; |
| } |
| |
| public void calledFunction() { |
| synchronized (lock) { // line +0 |
| cnt++; // line +1 |
| // We put a breakpoint here and PopFrame when we are at it. |
| doNothing(); // line +3 |
| } |
| } |
| |
| public String toString() { |
| return "SynchronizedTestObject { cnt: " + cnt + " }"; |
| } |
| } |
| |
| public static class ExceptionCatchTestObject extends AbstractTestObject implements Runnable { |
| public static class TestError extends Error {} |
| |
| public int cnt; |
| public ExceptionCatchTestObject() { |
| super(); |
| cnt = 0; |
| } |
| |
| public void calledFunction() { |
| cnt++; |
| try { |
| doThrow(); |
| } catch (TestError e) { |
| System.out.println(e.getClass().getName() + " caught in called function."); |
| } |
| } |
| |
| public void doThrow() { |
| throw new TestError(); |
| } |
| |
| public String toString() { |
| return "ExceptionCatchTestObject { cnt: " + cnt + " }"; |
| } |
| } |
| |
| public static class ExceptionThrowFarTestObject implements TestRunnable { |
| public static class TestError extends Error {} |
| |
| public int cnt; |
| public int baseCallCnt; |
| public final boolean catchInCalled; |
| public ExceptionThrowFarTestObject(boolean catchInCalled) { |
| super(); |
| cnt = 0; |
| baseCallCnt = 0; |
| this.catchInCalled = catchInCalled; |
| } |
| |
| public int getBaseCallCount() { |
| return baseCallCnt; |
| } |
| |
| public void run() { |
| baseCallCnt++; |
| try { |
| callingFunction(); |
| } catch (TestError e) { |
| System.out.println(e.getClass().getName() + " thrown and caught!"); |
| } |
| } |
| |
| public void callingFunction() { |
| calledFunction(); |
| } |
| public void calledFunction() { |
| cnt++; |
| if (catchInCalled) { |
| try { |
| throw new TestError(); // We put a watch here. |
| } catch (TestError e) { |
| System.out.println(e.getClass().getName() + " caught in same function."); |
| } |
| } else { |
| throw new TestError(); // We put a watch here. |
| } |
| } |
| |
| public Method getCallingMethod() throws Exception { |
| return this.getClass().getMethod("callingFunction"); |
| } |
| |
| public Method getCalledMethod() throws Exception { |
| return this.getClass().getMethod("calledFunction"); |
| } |
| |
| public String toString() { |
| return "ExceptionThrowFarTestObject { cnt: " + cnt + " }"; |
| } |
| } |
| |
| public static class ExceptionOnceObject extends AbstractTestObject { |
| public static final class TestError extends Error {} |
| public int cnt; |
| public final boolean throwInSub; |
| public ExceptionOnceObject(boolean throwInSub) { |
| super(); |
| cnt = 0; |
| this.throwInSub = throwInSub; |
| } |
| |
| public void calledFunction() { |
| cnt++; |
| if (cnt == 1) { |
| if (throwInSub) { |
| doThrow(); |
| } else { |
| throw new TestError(); |
| } |
| } |
| } |
| |
| public void doThrow() { |
| throw new TestError(); |
| } |
| |
| public String toString() { |
| return "ExceptionOnceObject { cnt: " + cnt + ", throwInSub: " + throwInSub + " }"; |
| } |
| } |
| |
| public static class ExceptionThrowTestObject implements TestRunnable { |
| public static class TestError extends Error {} |
| |
| public int cnt; |
| public int baseCallCnt; |
| public final boolean catchInCalled; |
| public ExceptionThrowTestObject(boolean catchInCalled) { |
| super(); |
| cnt = 0; |
| baseCallCnt = 0; |
| this.catchInCalled = catchInCalled; |
| } |
| |
| public int getBaseCallCount() { |
| return baseCallCnt; |
| } |
| |
| public void run() { |
| baseCallCnt++; |
| try { |
| calledFunction(); |
| } catch (TestError e) { |
| System.out.println(e.getClass().getName() + " thrown and caught!"); |
| } |
| } |
| |
| public void calledFunction() { |
| cnt++; |
| if (catchInCalled) { |
| try { |
| throw new TestError(); // We put a watch here. |
| } catch (TestError e) { |
| System.out.println(e.getClass().getName() + " caught in same function."); |
| } |
| } else { |
| throw new TestError(); // We put a watch here. |
| } |
| } |
| |
| public Method getCalledMethod() throws Exception { |
| return this.getClass().getMethod("calledFunction"); |
| } |
| |
| public String toString() { |
| return "ExceptionThrowTestObject { cnt: " + cnt + " }"; |
| } |
| } |
| |
| public static class NativeCalledObject extends AbstractTestObject { |
| public int cnt = 0; |
| |
| public native void calledFunction(); |
| |
| public String toString() { |
| return "NativeCalledObject { cnt: " + cnt + " }"; |
| } |
| } |
| |
| public static class NativeCallerObject implements TestRunnable { |
| public int baseCnt = 0; |
| public int cnt = 0; |
| |
| public int getBaseCallCount() { |
| return baseCnt; |
| } |
| |
| public native void run(); |
| |
| public void calledFunction() { |
| cnt++; |
| // We will stop using a MethodExit event. |
| } |
| |
| public Method getCalledMethod() throws Exception { |
| return this.getClass().getMethod("calledFunction"); |
| } |
| |
| public String toString() { |
| return "NativeCallerObject { cnt: " + cnt + " }"; |
| } |
| } |
| public static class SuspendSuddenlyObject extends AbstractTestObject { |
| public volatile boolean stop_spinning = false; |
| public volatile boolean is_spinning = false; |
| public int cnt = 0; |
| |
| public void calledFunction() { |
| cnt++; |
| while (!stop_spinning) { |
| is_spinning = true; |
| } |
| } |
| |
| public String toString() { |
| return "SuspendSuddenlyObject { cnt: " + cnt + " }"; |
| } |
| } |
| |
| public static void run(boolean canRunClassLoadTests) throws Exception { |
| new Test1953(canRunClassLoadTests, (x)-> {}).runTests(); |
| } |
| |
| // This entrypoint is used by CTS only. */ |
| public static void run() throws Exception { |
| /* TODO: Due to the way that CTS tests are verified we cannot run class-load-tests since the |
| * verifier will be delayed until runtime and then load the classes all at once. This |
| * makes the test impossible to run. |
| */ |
| run(/*canRunClassLoadTests*/ false); |
| } |
| |
| public Test1953(boolean canRunClassLoadTests, Consumer<TestRunnable> preTest) { |
| this.canRunClassLoadTests = canRunClassLoadTests; |
| this.preTest = preTest; |
| } |
| |
| private Consumer<TestRunnable> preTest; |
| |
| public void runTests() throws Exception { |
| setupTest(); |
| |
| final Method calledFunction = StandardTestObject.class.getDeclaredMethod("calledFunction"); |
| final Method doNothingMethod = Test1953.class.getDeclaredMethod("doNothing"); |
| // Add a breakpoint on the second line after the start of the function |
| final int line = Breakpoint.locationToLine(calledFunction, 0) + 2; |
| final long loc = Breakpoint.lineToLocation(calledFunction, line); |
| System.out.println("Test stopped using breakpoint"); |
| runTestOn(new StandardTestObject(), |
| (thr) -> setupSuspendBreakpointFor(calledFunction, loc, thr), |
| SuspendEvents::clearSuspendBreakpointFor); |
| |
| final Method syncFunctionCalledFunction = |
| SynchronizedFunctionTestObject.class.getDeclaredMethod("calledFunction"); |
| // Add a breakpoint on the second line after the start of the function |
| // Annoyingly r8 generally has the first instruction (a monitor enter) not be marked as being |
| // on any line but javac has it marked as being on the first line of the function. Just use the |
| // second entry on the line-number table to get the breakpoint. This should be good for both. |
| final long syncFunctionLoc = |
| Breakpoint.getLineNumberTable(syncFunctionCalledFunction)[1].location; |
| System.out.println("Test stopped using breakpoint with declared synchronized function"); |
| runTestOn(new SynchronizedFunctionTestObject(), |
| (thr) -> setupSuspendBreakpointFor(syncFunctionCalledFunction, syncFunctionLoc, thr), |
| SuspendEvents::clearSuspendBreakpointFor); |
| |
| final Method syncCalledFunction = |
| SynchronizedTestObject.class.getDeclaredMethod("calledFunction"); |
| // Add a breakpoint on the second line after the start of the function |
| final int syncLine = Breakpoint.locationToLine(syncCalledFunction, 0) + 3; |
| final long syncLoc = Breakpoint.lineToLocation(syncCalledFunction, syncLine); |
| System.out.println("Test stopped using breakpoint with synchronized block"); |
| Object lock = new Object(); |
| synchronized (lock) {} |
| runTestOn(new SynchronizedTestObject(lock), |
| (thr) -> setupSuspendBreakpointFor(syncCalledFunction, syncLoc, thr), |
| SuspendEvents::clearSuspendBreakpointFor); |
| synchronized (lock) {} |
| |
| System.out.println("Test stopped on single step"); |
| runTestOn(new StandardTestObject(), |
| (thr) -> setupSuspendSingleStepAt(calledFunction, loc, thr), |
| SuspendEvents::clearSuspendSingleStepFor); |
| |
| final Field target_field = FieldBasedTestObject.class.getDeclaredField("TARGET_FIELD"); |
| System.out.println("Test stopped on field access"); |
| runTestOn(new FieldBasedTestObject(), |
| (thr) -> setupFieldSuspendFor(FieldBasedTestObject.class, target_field, true, thr), |
| SuspendEvents::clearFieldSuspendFor); |
| |
| System.out.println("Test stopped on field modification"); |
| runTestOn(new FieldBasedTestObject(), |
| (thr) -> setupFieldSuspendFor(FieldBasedTestObject.class, target_field, false, thr), |
| SuspendEvents::clearFieldSuspendFor); |
| |
| System.out.println("Test stopped during Method Exit of doNothing"); |
| runTestOn(new StandardTestObject(false), |
| (thr) -> setupSuspendMethodEvent(doNothingMethod, /*enter*/ false, thr), |
| SuspendEvents::clearSuspendMethodEvent); |
| |
| // NB We need another test to make sure the MethodEntered event is triggered twice. |
| System.out.println("Test stopped during Method Enter of doNothing"); |
| runTestOn(new StandardTestObject(false), |
| (thr) -> setupSuspendMethodEvent(doNothingMethod, /*enter*/ true, thr), |
| SuspendEvents::clearSuspendMethodEvent); |
| |
| System.out.println("Test stopped during Method Exit of calledFunction"); |
| runTestOn(new StandardTestObject(false), |
| (thr) -> setupSuspendMethodEvent(calledFunction, /*enter*/ false, thr), |
| SuspendEvents::clearSuspendMethodEvent); |
| |
| System.out.println("Test stopped during Method Enter of calledFunction"); |
| runTestOn(new StandardTestObject(false), |
| (thr) -> setupSuspendMethodEvent(calledFunction, /*enter*/ true, thr), |
| SuspendEvents::clearSuspendMethodEvent); |
| |
| final Method exceptionOnceCalledMethod = |
| ExceptionOnceObject.class.getDeclaredMethod("calledFunction"); |
| System.out.println("Test stopped during Method Exit due to exception thrown in same function"); |
| runTestOn(new ExceptionOnceObject(/*throwInSub*/ false), |
| (thr) -> setupSuspendMethodEvent(exceptionOnceCalledMethod, /*enter*/ false, thr), |
| SuspendEvents::clearSuspendMethodEvent); |
| |
| System.out.println("Test stopped during Method Exit due to exception thrown in subroutine"); |
| runTestOn(new ExceptionOnceObject(/*throwInSub*/ true), |
| (thr) -> setupSuspendMethodEvent(exceptionOnceCalledMethod, /*enter*/ false, thr), |
| SuspendEvents::clearSuspendMethodEvent); |
| |
| final Method exceptionThrowCalledMethod = |
| ExceptionThrowTestObject.class.getDeclaredMethod("calledFunction"); |
| final Method exceptionCatchThrowMethod = |
| ExceptionCatchTestObject.class.getDeclaredMethod("doThrow"); |
| // Disable more often then we technically need to in order to avoid the need |
| // for a huge number of possible results and allow the test to be easily |
| // used in CTS. |
| if (IS_ART && canRunClassLoadTests && CanRunClassLoadingTests()) { |
| System.out.println("Test stopped during notifyFramePop without exception on pop of calledFunction"); |
| runTestOn(new StandardTestObject(false), |
| (thr) -> setupSuspendPopFrameEvent(1, doNothingMethod, thr), |
| SuspendEvents::clearSuspendPopFrameEvent); |
| |
| System.out.println("Test stopped during notifyFramePop without exception on pop of doNothing"); |
| runTestOn(new StandardTestObject(false), |
| (thr) -> setupSuspendPopFrameEvent(0, doNothingMethod, thr), |
| SuspendEvents::clearSuspendPopFrameEvent); |
| |
| System.out.println("Test stopped during notifyFramePop with exception on pop of calledFunction"); |
| runTestOn(new ExceptionThrowTestObject(false), |
| (thr) -> setupSuspendPopFrameEvent(0, exceptionThrowCalledMethod, thr), |
| SuspendEvents::clearSuspendPopFrameEvent); |
| |
| System.out.println("Test stopped during notifyFramePop with exception on pop of doThrow"); |
| runTestOn(new ExceptionCatchTestObject(), |
| (thr) -> setupSuspendPopFrameEvent(0, exceptionCatchThrowMethod, thr), |
| SuspendEvents::clearSuspendPopFrameEvent); |
| } |
| |
| System.out.println("Test stopped during ExceptionCatch event of calledFunction " + |
| "(catch in called function, throw in called function)"); |
| runTestOn(new ExceptionThrowTestObject(true), |
| (thr) -> setupSuspendExceptionEvent(exceptionThrowCalledMethod, /*catch*/ true, thr), |
| SuspendEvents::clearSuspendExceptionEvent); |
| |
| final Method exceptionCatchCalledMethod = |
| ExceptionCatchTestObject.class.getDeclaredMethod("calledFunction"); |
| System.out.println("Test stopped during ExceptionCatch event of calledFunction " + |
| "(catch in called function, throw in subroutine)"); |
| runTestOn(new ExceptionCatchTestObject(), |
| (thr) -> setupSuspendExceptionEvent(exceptionCatchCalledMethod, /*catch*/ true, thr), |
| SuspendEvents::clearSuspendExceptionEvent); |
| |
| System.out.println("Test stopped during Exception event of calledFunction " + |
| "(catch in calling function)"); |
| runTestOn(new ExceptionThrowTestObject(false), |
| (thr) -> setupSuspendExceptionEvent(exceptionThrowCalledMethod, /*catch*/ false, thr), |
| SuspendEvents::clearSuspendExceptionEvent); |
| |
| System.out.println("Test stopped during Exception event of calledFunction " + |
| "(catch in called function)"); |
| runTestOn(new ExceptionThrowTestObject(true), |
| (thr) -> setupSuspendExceptionEvent(exceptionThrowCalledMethod, /*catch*/ false, thr), |
| SuspendEvents::clearSuspendExceptionEvent); |
| |
| final Method exceptionThrowFarCalledMethod = |
| ExceptionThrowFarTestObject.class.getDeclaredMethod("calledFunction"); |
| System.out.println("Test stopped during Exception event of calledFunction " + |
| "(catch in parent of calling function)"); |
| runTestOn(new ExceptionThrowFarTestObject(false), |
| (thr) -> setupSuspendExceptionEvent(exceptionThrowFarCalledMethod, /*catch*/ false, thr), |
| SuspendEvents::clearSuspendExceptionEvent); |
| |
| System.out.println("Test stopped during Exception event of calledFunction " + |
| "(catch in called function)"); |
| runTestOn(new ExceptionThrowFarTestObject(true), |
| (thr) -> setupSuspendExceptionEvent(exceptionThrowFarCalledMethod, /*catch*/ false, thr), |
| SuspendEvents::clearSuspendExceptionEvent); |
| |
| // These tests are disabled for either the RI (b/116003018) or for jvmti-stress. For the |
| // later it is due to the additional agent causing classes to be loaded earlier as it forces |
| // deeper verification during class redefinition, causing failures. |
| // NB the agent is prevented from popping frames in either of these events in ART. See |
| // b/117615146 for more information about this restriction. |
| if (canRunClassLoadTests && CanRunClassLoadingTests()) { |
| // This test doesn't work on RI since the RI disallows use of PopFrame during a ClassLoad |
| // event. See b/116003018 for more information. |
| System.out.println("Test stopped during a ClassLoad event."); |
| runTestOn(new ClassLoadObject(), |
| (thr) -> setupSuspendClassEvent(EVENT_TYPE_CLASS_LOAD, ClassLoadObject.CLASS_NAMES, thr), |
| SuspendEvents::clearSuspendClassEvent); |
| |
| // The RI handles a PopFrame during a ClassPrepare event incorrectly. See b/116003018 for |
| // more information. |
| System.out.println("Test stopped during a ClassPrepare event."); |
| runTestOn(new ClassLoadObject(), |
| (thr) -> setupSuspendClassEvent(EVENT_TYPE_CLASS_PREPARE, |
| ClassLoadObject.CLASS_NAMES, |
| thr), |
| SuspendEvents::clearSuspendClassEvent); |
| } |
| System.out.println("Test stopped during random Suspend."); |
| final SuspendSuddenlyObject sso = new SuspendSuddenlyObject(); |
| runTestOn( |
| sso, |
| new TestSuspender() { |
| public void setup(Thread thr) { } |
| public void waitForSuspend(Thread thr) { |
| while (!sso.is_spinning) {} |
| Suspension.suspend(thr); |
| } |
| public void cleanup(Thread thr) { |
| sso.stop_spinning = true; |
| } |
| }); |
| |
| final Method redefineCalledFunction = |
| RedefineTestObject.class.getDeclaredMethod("calledFunction"); |
| final int redefLine = Breakpoint.locationToLine(redefineCalledFunction, 0) + 2; |
| final long redefLoc = Breakpoint.lineToLocation(redefineCalledFunction, redefLine); |
| System.out.println("Test redefining frame being popped."); |
| runTestOn(new RedefineTestObject(), |
| (thr) -> setupSuspendBreakpointFor(redefineCalledFunction, redefLoc, thr), |
| (thr) -> { |
| clearSuspendBreakpointFor(thr); |
| Redefinition.doCommonClassRedefinition(RedefineTestObject.class, |
| RedefineTestObject.CLASS_BYTES, |
| RedefineTestObject.DEX_BYTES); |
| }); |
| |
| System.out.println("Test stopped during a native method fails"); |
| runTestOn(new NativeCalledObject(), |
| SuspendEvents::setupWaitForNativeCall, |
| SuspendEvents::clearWaitForNativeCall); |
| |
| System.out.println("Test stopped in a method called by native fails"); |
| final Method nativeCallerMethod = NativeCallerObject.class.getDeclaredMethod("calledFunction"); |
| runTestOn(new NativeCallerObject(), |
| (thr) -> setupSuspendMethodEvent(nativeCallerMethod, /*enter*/ false, thr), |
| SuspendEvents::clearSuspendMethodEvent); |
| |
| |
| final Object lock2 = new Object(); |
| synchronized (lock2) {} |
| System.out.println("Test stopped with monitor in enclosing frame."); |
| runTestOn(new StandardTestObject() { |
| @Override |
| public void run() { |
| synchronized (lock2) { |
| super.run(); |
| } |
| } |
| }, |
| (thr) -> setupSuspendBreakpointFor(calledFunction, loc, thr), |
| SuspendEvents::clearSuspendBreakpointFor); |
| synchronized (lock2) {} |
| } |
| |
| // Volatile is to prevent any future optimizations that could invalidate this test by doing |
| // constant propagation and eliminating the failing paths before the verifier is able to load the |
| // class. |
| static volatile boolean ranClassLoadTest = false; |
| static boolean classesPreverified = false; |
| private static final class RCLT0 { public void foo() {} } |
| private static final class RCLT1 { public void foo() {} } |
| // If classes are not preverified for some reason (interp-ac, no-image, etc) the verifier will |
| // actually load classes as it runs. This means that we cannot use the class-load tests as they |
| // are written. TODO Support this. |
| public boolean CanRunClassLoadingTests() { |
| if (ranClassLoadTest) { |
| return classesPreverified; |
| } |
| if (!ranClassLoadTest) { |
| // Only this will ever be executed. |
| new RCLT0().foo(); |
| } else { |
| // This will never be executed. If classes are not preverified the verifier will load RCLT1 |
| // when the enclosing method is run. This behavior makes the class-load/prepare test cases |
| // impossible to successfully run (they will deadlock). |
| new RCLT1().foo(); |
| System.out.println("FAILURE: UNREACHABLE Location!"); |
| } |
| classesPreverified = !isClassLoaded("Lart/Test1953$RCLT1;"); |
| ranClassLoadTest = true; |
| return classesPreverified; |
| } |
| |
| public static native boolean isClassLoaded(String name); |
| public static native void popFrame(Thread thr); |
| } |