/* * Copyright (C) 2016 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 static art.Redefinition.doCommonClassRedefinition; import java.util.Base64; import java.util.function.Consumer; import java.lang.reflect.Method; public class Main { static final boolean ALWAYS_PRINT = false; // import java.util.function.Consumer; // class Transform { // public void sayHi(int recur, Consumer reporter, Runnable r) { // privateSayHi(recur, reporter, r); // } // private void privateSayHi(int recur, Consumer reporter, Runnable r) { // reporter.accpet("hello" + recur + " - transformed"); // if (recur == 1) { // r.run(); // privateSayHi(recur - 1, reporter, r); // } else if (recur != 0) { // privateSayHi(recur - 1, reporter, r); // } // reporter.accept("goodbye" + recur + " - transformed"); // } // } private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( "yv66vgAAADQANAoADgAbCgANABwHAB0KAAMAGwgAHgoAAwAfCgADACAIACEKAAMAIgsAIwAkCwAl" + "ACYIACcHACgHACkBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAFc2F5" + "SGkBADUoSUxqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXI7TGphdmEvbGFuZy9SdW5uYWJsZTsp" + "VgEACVNpZ25hdHVyZQEASShJTGphdmEvdXRpbC9mdW5jdGlvbi9Db25zdW1lcjxMamF2YS9sYW5n" + "L1N0cmluZzs+O0xqYXZhL2xhbmcvUnVubmFibGU7KVYBAAxwcml2YXRlU2F5SGkBAA1TdGFja01h" + "cFRhYmxlAQAKU291cmNlRmlsZQEADlRyYW5zZm9ybS5qYXZhDAAPABAMABcAFAEAF2phdmEvbGFu" + "Zy9TdHJpbmdCdWlsZGVyAQAFaGVsbG8MACoAKwwAKgAsAQAOIC0gdHJhbnNmb3JtZWQMAC0ALgcA" + "LwwAMAAxBwAyDAAzABABAAdnb29kYnllAQAJVHJhbnNmb3JtAQAQamF2YS9sYW5nL09iamVjdAEA" + "BmFwcGVuZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEA" + "HChJKUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsBAAh0b1N0cmluZwEAFCgpTGphdmEvbGFuZy9T" + "dHJpbmc7AQAbamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyAQAGYWNjZXB0AQAVKExqYXZhL2xh" + "bmcvT2JqZWN0OylWAQASamF2YS9sYW5nL1J1bm5hYmxlAQADcnVuACAADQAOAAAAAAADAAAADwAQ" + "AAEAEQAAAB0AAQABAAAABSq3AAGxAAAAAQASAAAABgABAAAAAgABABMAFAACABEAAAAkAAQABAAA" + "AAgqGywttwACsQAAAAEAEgAAAAoAAgAAAAQABwAFABUAAAACABYAAgAXABQAAgARAAAAnwAEAAQA" + "AABhLLsAA1m3AAQSBbYABhu2AAcSCLYABrYACbkACgIAGwSgABUtuQALAQAqGwRkLC23AAKnABAb" + "mQAMKhsEZCwttwACLLsAA1m3AAQSDLYABhu2AAcSCLYABrYACbkACgIAsQAAAAIAEgAAACIACAAA" + "AAcAHgAIACMACQApAAoANQALADkADABCAA4AYAAPABgAAAAEAAI1DAAVAAAAAgAWAAEAGQAAAAIA" + "Gg=="); private static final byte[] DEX_BYTES = Base64.getDecoder().decode( "ZGV4CjAzNQCevtlr8B0kh/duuDYqXkGz/w9lMmtCCuRoBQAAcAAAAHhWNBIAAAAAAAAAALAEAAAg" + "AAAAcAAAAAkAAADwAAAABgAAABQBAAAAAAAAAAAAAAoAAABcAQAAAQAAAKwBAACcAwAAzAEAAPYC" + "AAAGAwAACgMAAA4DAAARAwAAGQMAAB0DAAAgAwAAIwMAACcDAAArAwAAOAMAAFcDAABrAwAAgQMA" + "AJUDAACwAwAAzgMAAO0DAAD9AwAAAAQAAAYEAAAKBAAAEgQAABoEAAAuBAAANwQAAD4EAABMBAAA" + "UQQAAFgEAABiBAAABgAAAAoAAAALAAAADAAAAA0AAAAOAAAADwAAABEAAAATAAAABwAAAAUAAAAA" + "AAAACAAAAAYAAADUAgAACQAAAAYAAADcAgAAEwAAAAgAAAAAAAAAFAAAAAgAAADkAgAAFQAAAAgA" + "AADwAgAAAQADAAQAAAABAAQAGwAAAAEABAAdAAAAAwADAAQAAAAEAAMAHAAAAAYAAwAEAAAABgAB" + "ABcAAAAGAAIAFwAAAAYAAAAeAAAABwAFABYAAAABAAAAAAAAAAMAAAAAAAAAEgAAALQCAACeBAAA" + "AAAAAAEAAACKBAAAAQABAAEAAABpBAAABAAAAHAQAwAAAA4ABgAEAAQAAABuBAAAUAAAACIABgBw" + "EAUAAAAbARoAAABuIAcAEAAMAG4gBgAwAAwAGwEAAAAAbiAHABAADABuEAgAAAAMAHIgCQAEABIQ" + "MwMpAHIQBAAFANgAA/9wQAEAAlQiAAYAcBAFAAAAGwEZAAAAbiAHABAADABuIAYAMAAMABsBAAAA" + "AG4gBwAQAAwAbhAIAAAADAByIAkABAAOADgD4f/YAAP/cEABAAJUKNoEAAQABAAAAIEEAAAEAAAA" + "cEABABAyDgAAAAAAAAAAAAIAAAAAAAAAAQAAAMwBAAACAAAAzAEAAAEAAAAAAAAAAQAAAAUAAAAD" + "AAAAAAAHAAQAAAABAAAAAwAOIC0gdHJhbnNmb3JtZWQAAihJAAIpVgABPAAGPGluaXQ+AAI+OwAB" + "SQABTAACTEkAAkxMAAtMVHJhbnNmb3JtOwAdTGRhbHZpay9hbm5vdGF0aW9uL1NpZ25hdHVyZTsA" + "EkxqYXZhL2xhbmcvT2JqZWN0OwAUTGphdmEvbGFuZy9SdW5uYWJsZTsAEkxqYXZhL2xhbmcvU3Ry" + "aW5nOwAZTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwAcTGphdmEvdXRpbC9mdW5jdGlvbi9Db25z" + "dW1lcgAdTGphdmEvdXRpbC9mdW5jdGlvbi9Db25zdW1lcjsADlRyYW5zZm9ybS5qYXZhAAFWAARW" + "SUxMAAJWTAAGYWNjZXB0AAZhcHBlbmQAEmVtaXR0ZXI6IGphY2stNC4yNAAHZ29vZGJ5ZQAFaGVs" + "bG8ADHByaXZhdGVTYXlIaQADcnVuAAVzYXlIaQAIdG9TdHJpbmcABXZhbHVlAAIABw4ABwMAAAAH" + "DgEeDzw8XQEeDxktAAQDAAAABw48AAICAR8cBxcBFxAXAxcOFwUXDRcCAAACAQCAgATUAwEC7AMC" + "AZwFDwAAAAAAAAABAAAAAAAAAAEAAAAgAAAAcAAAAAIAAAAJAAAA8AAAAAMAAAAGAAAAFAEAAAUA" + "AAAKAAAAXAEAAAYAAAABAAAArAEAAAMQAAABAAAAzAEAAAEgAAADAAAA1AEAAAYgAAABAAAAtAIA" + "AAEQAAAEAAAA1AIAAAIgAAAgAAAA9gIAAAMgAAADAAAAaQQAAAQgAAABAAAAigQAAAAgAAABAAAA" + "ngQAAAAQAAABAAAAsAQAAA=="); // A class that we can use to keep track of the output of this test. private static class TestWatcher implements Consumer { private StringBuilder sb; public TestWatcher() { sb = new StringBuilder(); } @Override public void accept(String s) { if (Main.ALWAYS_PRINT) { System.out.println(s); } sb.append(s); sb.append('\n'); } public String getOutput() { return sb.toString(); } public void clear() { sb = new StringBuilder(); } } public static void main(String[] args) { doTest(new Transform()); } private static boolean retry = false; public static void doTest(Transform t) { final TestWatcher reporter = new TestWatcher(); Method say_hi_method; Method private_say_hi_method; // Figure out if we can even JIT at all. final boolean has_jit = hasJit(); try { say_hi_method = Transform.class.getDeclaredMethod( "sayHi", int.class, Consumer.class, Runnable.class); private_say_hi_method = Transform.class.getDeclaredMethod( "privateSayHi", int.class, Consumer.class, Runnable.class); } catch (Exception e) { System.out.println("Unable to find methods!"); e.printStackTrace(System.out); return; } // Makes sure the stack is the way we want it for the test and does the redefinition. It will // set the retry boolean to true if we need to go around again due to jit code being GCd. Runnable do_redefinition = () -> { if (has_jit && (Main.isInterpretedFunction(say_hi_method, true) || Main.isInterpretedFunction(private_say_hi_method, true))) { // Try again. We are not running the right jitted methods/cannot redefine them now. retry = true; } else { // Actually do the redefinition. The stack looks good. retry = false; reporter.accept("transforming calling function"); doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES); } }; do { // Run ensureJitCompiled here since it might get GCd ensureJitCompiled(Transform.class, "sayHi"); ensureJitCompiled(Transform.class, "privateSayHi"); // Clear output. reporter.clear(); t.sayHi(2, reporter, () -> { reporter.accept("Not doing anything here"); }); t.sayHi(2, reporter, do_redefinition); t.sayHi(2, reporter, () -> { reporter.accept("Not doing anything here"); }); } while(retry); System.out.println(reporter.getOutput()); } private static native boolean hasJit(); private static native boolean isInterpretedFunction(Method m, boolean require_deoptimizable); private static native void ensureJitCompiled(Class c, String name); }