/*
* 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 void sayHi(Consumer<Consumer<String>> r, Consumer<String> 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(
"yv66vgAAADUAGwoABgARCAASCwATABQIABUHABYHABcBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAP"
+ "TGluZU51bWJlclRhYmxlAQAFc2F5SGkBAD0oTGphdmEvdXRpbC9mdW5jdGlvbi9Db25zdW1lcjtM"
+ "amF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyOylWAQAJU2lnbmF0dXJlAQCEKExqYXZhL3V0aWwv"
+ "ZnVuY3Rpb24vQ29uc3VtZXI8TGphdmEvdXRpbC9mdW5jdGlvbi9Db25zdW1lcjxMamF2YS9sYW5n"
+ "L1N0cmluZzs+Oz47TGphdmEvdXRpbC9mdW5jdGlvbi9Db25zdW1lcjxMamF2YS9sYW5nL1N0cmlu"
+ "Zzs+OylWAQAKU291cmNlRmlsZQEADlRyYW5zZm9ybS5qYXZhDAAHAAgBABxnb29kYnllIC0gU3Rh"
+ "cnQgbWV0aG9kIHNheUhpBwAYDAAZABoBABpnb29kYnllIC0gRW5kIG1ldGhvZCBzYXlIaQEACVRy"
+ "YW5zZm9ybQEAEGphdmEvbGFuZy9PYmplY3QBABtqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXIB"
+ "AAZhY2NlcHQBABUoTGphdmEvbGFuZy9PYmplY3Q7KVYAIAAFAAYAAAAAAAIAAAAHAAgAAQAJAAAA"
+ "HQABAAEAAAAFKrcAAbEAAAABAAoAAAAGAAEAAAAHAAEACwAMAAIACQAAADwAAgADAAAAGCwSArkA"
+ "AwIAKyy5AAMCACwSBLkAAwIAsQAAAAEACgAAABIABAAAAAkACAAKAA8ACwAXAAwADQAAAAIADgAB"
+ "AA8AAAACABA=");
private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
"ZGV4CjAzNQA8rBr8fYSjBsIDrOiAknAnKu+2xbIe3RAsBAAAcAAAAHhWNBIAAAAAAAAAAHQDAAAU"
+ "AAAAcAAAAAUAAADAAAAAAwAAANQAAAAAAAAAAAAAAAQAAAD4AAAAAQAAABgBAAD0AgAAOAEAAJwB"
+ "AACfAQAApwEAAK0BAACzAQAAwAEAAN8BAADzAQAABwIAACYCAABFAgAAVQIAAFgCAABcAgAAYQIA"
+ "AGkCAACFAgAAowIAAKoCAACxAgAABAAAAAUAAAAGAAAACAAAAAsAAAALAAAABAAAAAAAAAAMAAAA"
+ "BAAAAIwBAAANAAAABAAAAJQBAAAAAAAAAQAAAAAAAgARAAAAAgAAAAEAAAADAAEADgAAAAAAAAAA"
+ "AAAAAgAAAAAAAAAKAAAAXAMAAD8DAAAAAAAAAQABAAEAAAB8AQAABAAAAHAQAgAAAA4ABAADAAIA"
+ "AACAAQAADgAAABoAEAByIAMAAwByIAMAMgAaAg8AciADACMADgAHAA4ACQIAAA5aPFoAAAAAAQAA"
+ "AAIAAAACAAAAAwADAAEoAAY8aW5pdD4ABD47KVYABD47PjsAC0xUcmFuc2Zvcm07AB1MZGFsdmlr"
+ "L2Fubm90YXRpb24vU2lnbmF0dXJlOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0"
+ "cmluZzsAHUxqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXI7AB1MamF2YS91dGlsL2Z1bmN0aW9u"
+ "L0NvbnN1bWVyPAAOVHJhbnNmb3JtLmphdmEAAVYAAlZMAANWTEwABmFjY2VwdAAaZ29vZGJ5ZSAt"
+ "IEVuZCBtZXRob2Qgc2F5SGkAHGdvb2RieWUgLSBTdGFydCBtZXRob2Qgc2F5SGkABXNheUhpAAV2"
+ "YWx1ZQB2fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6MSwic2hhLTEi"
+ "OiI3MTExYTM1YmFlNmQ1MTg1ZGNmYjMzOGQ2MTA3NGFjYTg0MjZjMDA2IiwidmVyc2lvbiI6IjEu"
+ "NS4xNC1kZXYifQACAQESHAgXABcJFwkXBxcDFwkXBxcCAAABAQCAgAS4AgEB0AIAAAAAAAAAAQAA"
+ "ACkDAABQAwAAAAAAAAEAAAAAAAAAAQAAAFQDAAAPAAAAAAAAAAEAAAAAAAAAAQAAABQAAABwAAAA"
+ "AgAAAAUAAADAAAAAAwAAAAMAAADUAAAABQAAAAQAAAD4AAAABgAAAAEAAAAYAQAAASAAAAIAAAA4"
+ "AQAAAyAAAAIAAAB8AQAAARAAAAIAAACMAQAAAiAAABQAAACcAQAABCAAAAEAAAApAwAAACAAAAEA"
+ "AAA/AwAAAxAAAAIAAABQAwAABiAAAAEAAABcAwAAABAAAAEAAAB0AwAA");
// A class that we can use to keep track of the output of this test.
private static class TestWatcher implements Consumer<String> {
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<Consumer<String>> say_nothing = (Consumer<String> w) -> {
w.accept("Not doing anything here");
};
// Run ensureJitCompiled here since it might get GCd
ensureJitCompiled(Transform.class, "sayHi");
final CountDownLatch arrive = new CountDownLatch(NUM_THREADS);
final CountDownLatch depart = new CountDownLatch(1);
Consumer<Consumer<String>> request_redefine = (Consumer<String> 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<Consumer<String>> do_nothing;
private Consumer<Consumer<String>> request_redefinition;
public TestThread(Transform t,
TestWatcher w,
Consumer<Consumer<String>> do_nothing,
Consumer<Consumer<String>> 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.sayHi(do_nothing, w);
t.sayHi(request_redefinition, w);
t.sayHi(do_nothing, w);
}
}
private static native boolean hasJit();
private static native void ensureJitCompiled(Class c, String name);
}