/* * Copyright (C) 2019 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. */ import art.Redefinition; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Base64; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Phaser; import java.util.function.Consumer; public class Main { public static final int NUM_THREADS = 10; public static final boolean PRINT = false; // import java.util.function.Consumer; // // class Transform { // public native void nativeSayHi(Consumer> r, Consumer rep); // public void sayHi(Consumer> r, Consumer reporter) { // reporter.accept("goodbye - Start method sayHi"); // r.accept(reporter); // reporter.accept("goodbye - End method sayHi"); // } // } private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( "yv66vgAAADUAHAoABgASCAATCwAUABUIABYHABcHABgBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAP" + "TGluZU51bWJlclRhYmxlAQALbmF0aXZlU2F5SGkBAD0oTGphdmEvdXRpbC9mdW5jdGlvbi9Db25z" + "dW1lcjtMamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyOylWAQAJU2lnbmF0dXJlAQCEKExqYXZh" + "L3V0aWwvZnVuY3Rpb24vQ29uc3VtZXI8TGphdmEvdXRpbC9mdW5jdGlvbi9Db25zdW1lcjxMamF2" + "YS9sYW5nL1N0cmluZzs+Oz47TGphdmEvdXRpbC9mdW5jdGlvbi9Db25zdW1lcjxMamF2YS9sYW5n" + "L1N0cmluZzs+OylWAQAFc2F5SGkBAApTb3VyY2VGaWxlAQAOVHJhbnNmb3JtLmphdmEMAAcACAEA" + "HGdvb2RieWUgLSBTdGFydCBtZXRob2Qgc2F5SGkHABkMABoAGwEAGmdvb2RieWUgLSBFbmQgbWV0" + "aG9kIHNheUhpAQAJVHJhbnNmb3JtAQAQamF2YS9sYW5nL09iamVjdAEAG2phdmEvdXRpbC9mdW5j" + "dGlvbi9Db25zdW1lcgEABmFjY2VwdAEAFShMamF2YS9sYW5nL09iamVjdDspVgAgAAUABgAAAAAA" + "AwAAAAcACAABAAkAAAAdAAEAAQAAAAUqtwABsQAAAAEACgAAAAYAAQAAAAcBAQALAAwAAQANAAAA" + "AgAOAAEADwAMAAIACQAAADwAAgADAAAAGCwSArkAAwIAKyy5AAMCACwSBLkAAwIAsQAAAAEACgAA" + "ABIABAAAABAACAARAA8AEgAXABMADQAAAAIADgABABAAAAACABE="); private static final byte[] DEX_BYTES = Base64.getDecoder().decode( "ZGV4CjAzNQAztWgsKV3wmz41jXurCJpvXfxhxtK7W8NQBAAAcAAAAHhWNBIAAAAAAAAAAJgDAAAV" + "AAAAcAAAAAUAAADEAAAAAwAAANgAAAAAAAAAAAAAAAUAAAD8AAAAAQAAACQBAAAMAwAARAEAAKgB" + "AACrAQAAswEAALkBAAC/AQAAzAEAAOsBAAD/AQAAEwIAADICAABRAgAAYQIAAGQCAABoAgAAbQIA" + "AHUCAACRAgAArwIAALwCAADDAgAAygIAAAQAAAAFAAAABgAAAAgAAAALAAAACwAAAAQAAAAAAAAA" + "DAAAAAQAAACYAQAADQAAAAQAAACgAQAAAAAAAAEAAAAAAAIAEQAAAAAAAgASAAAAAgAAAAEAAAAD" + "AAEADgAAAAAAAAAAAAAAAgAAAAAAAAAKAAAAeAMAAFgDAAAAAAAAAQABAAEAAACIAQAABAAAAHAQ" + "AwAAAA4ABAADAAIAAACMAQAADgAAABoAEAByIAQAAwByIAQAMgAaAg8AciAEACMADgAHAA4AEAIA" + "AA5aPFoAAAAAAQAAAAIAAAACAAAAAwADAAEoAAY8aW5pdD4ABD47KVYABD47PjsAC0xUcmFuc2Zv" + "cm07AB1MZGFsdmlrL2Fubm90YXRpb24vU2lnbmF0dXJlOwASTGphdmEvbGFuZy9PYmplY3Q7ABJM" + "amF2YS9sYW5nL1N0cmluZzsAHUxqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXI7AB1MamF2YS91" + "dGlsL2Z1bmN0aW9uL0NvbnN1bWVyPAAOVHJhbnNmb3JtLmphdmEAAVYAAlZMAANWTEwABmFjY2Vw" + "dAAaZ29vZGJ5ZSAtIEVuZCBtZXRob2Qgc2F5SGkAHGdvb2RieWUgLSBTdGFydCBtZXRob2Qgc2F5" + "SGkAC25hdGl2ZVNheUhpAAVzYXlIaQAFdmFsdWUAdn5+RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJk" + "ZWJ1ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoiNzExMWEzNWJhZTZkNTE4NWRjZmIzMzhkNjEwNzRh" + "Y2E4NDI2YzAwNiIsInZlcnNpb24iOiIxLjUuMTQtZGV2In0AAgEBExwIFwAXCRcJFwcXAxcJFwcX" + "AgAAAQIAgIAExAIBgQIAAQHcAgAAAAAAAAEAAABCAwAAbAMAAAAAAAACAAAAAAAAAAEAAABwAwAA" + "AgAAAHADAAAPAAAAAAAAAAEAAAAAAAAAAQAAABUAAABwAAAAAgAAAAUAAADEAAAAAwAAAAMAAADY" + "AAAABQAAAAUAAAD8AAAABgAAAAEAAAAkAQAAASAAAAIAAABEAQAAAyAAAAIAAACIAQAAARAAAAIA" + "AACYAQAAAiAAABUAAACoAQAABCAAAAEAAABCAwAAACAAAAEAAABYAwAAAxAAAAIAAABsAwAABiAA" + "AAEAAAB4AwAAABAAAAEAAACYAwAA"); // A class that we can use to keep track of the output of this test. private static class TestWatcher implements Consumer { private StringBuilder sb; private String thread; public TestWatcher(String thread) { sb = new StringBuilder(); this.thread = thread; } @Override public void accept(String s) { String msg = thread + ": \t" + s; maybePrint(msg); sb.append(msg); sb.append('\n'); } public String getOutput() { return sb.toString(); } public void clear() { sb = new StringBuilder(); } } public static void main(String[] args) throws Exception { doTest(new Transform()); } private static boolean interpreting = true; public static void doTest(Transform t) throws Exception { TestWatcher[] watchers = new TestWatcher[NUM_THREADS]; for (int i = 0; i < NUM_THREADS; i++) { watchers[i] = new TestWatcher("Thread " + i); } // This just prints something out to show we are running the Runnable. Consumer> say_nothing = (Consumer w) -> { w.accept("Not doing anything here"); }; // Run ensureJitCompiled here since it might get GCd ensureJitCompiled(Transform.class, "nativeSayHi"); final CountDownLatch arrive = new CountDownLatch(NUM_THREADS); final CountDownLatch depart = new CountDownLatch(1); Consumer> request_redefine = (Consumer w) -> { try { arrive.countDown(); w.accept("Requesting redefinition"); depart.await(); } catch (Exception e) { throw new RuntimeException("Failed to do something", e); } }; Thread redefinition_thread = new RedefinitionThread(arrive, depart); redefinition_thread.start(); Thread[] threads = new Thread[NUM_THREADS]; for (int i = 0; i < NUM_THREADS; i++) { threads[i] = new TestThread(t, watchers[i], say_nothing, request_redefine); threads[i].start(); } redefinition_thread.join(); Arrays.stream(threads).forEach((thr) -> { try { thr.join(); } catch (Exception e) { throw new RuntimeException("Failed to join: ", e); } }); Arrays.stream(watchers).forEach((w) -> { System.out.println(w.getOutput()); }); } private static class RedefinitionThread extends Thread { private CountDownLatch arrivalLatch; private CountDownLatch departureLatch; public RedefinitionThread(CountDownLatch arrival, CountDownLatch departure) { super("Redefine thread!"); this.arrivalLatch = arrival; this.departureLatch = departure; } public void run() { // Figure out if we can even JIT at all. final boolean has_jit = hasJit(); try { this.arrivalLatch.await(); maybePrint("REDEFINITION THREAD: redefining something!"); Redefinition.doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES); maybePrint("REDEFINITION THREAD: redefined something!"); this.departureLatch.countDown(); } catch (Exception e) { e.printStackTrace(System.out); throw new RuntimeException("Failed to redefine", e); } } } private static synchronized void maybePrint(String s) { if (PRINT) { System.out.println(s); } } private static class TestThread extends Thread { private Transform t; private TestWatcher w; private Consumer> do_nothing; private Consumer> request_redefinition; public TestThread(Transform t, TestWatcher w, Consumer> do_nothing, Consumer> request_redefinition) { super(); this.t = t; this.w = w; this.do_nothing = do_nothing; this.request_redefinition = request_redefinition; } public void run() { w.clear(); t.nativeSayHi(do_nothing, w); t.nativeSayHi(request_redefinition, w); t.nativeSayHi(do_nothing, w); } } private static native boolean hasJit(); private static native void ensureJitCompiled(Class c, String name); }