/* * 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 java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; class Main1 { void foo(int i) { if (i != 1) { printError("error1"); } } void printError(String msg) { System.out.println(msg); } } class Main2 extends Main1 { void foo(int i) { if (i != 2) { printError("error2"); } } } class Proxied implements Runnable { public void run() { synchronized(Main.class) { Main.sOtherThreadStarted = true; // Wait for Main2 to be linked and deoptimization is triggered. try { Main.class.wait(); } catch (Exception e) { } } } } class MyInvocationHandler implements InvocationHandler { private final Proxied proxied; public MyInvocationHandler(Proxied proxied) { this.proxied = proxied; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(proxied, args); } } public class Main { static Main1 sMain1; static Main1 sMain2; static volatile boolean sOtherThreadStarted; // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked. // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined. // After Helper.createMain2() which links in Main2, live testOverride() on stack // should be deoptimized. static void testOverride() { sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2); // Wait for the other thread to start. while (!sOtherThreadStarted); // Create an Main2 instance and assign it to sMain2. // sMain1 is kept the same. sMain2 = Helper.createMain2(); // Wake up the other thread. synchronized(Main.class) { Main.class.notify(); } // There should be a deoptimization here right after Main2 is linked by // calling Helper.createMain2(), even though sMain1 didn't change. // The behavior here would be different if inline-cache is used, which // doesn't deoptimize since sMain1 still hits the type cache. sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2); if (sMain2 != null) { sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2); } } // Test scenarios under which CHA-based devirtualization happens, // and class loading that overrides a method can invalidate compiled code. // Also create a proxy method such that a proxy method's frame is visited // during stack walking. public static void main(String[] args) { System.loadLibrary(args[0]); // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet. sMain1 = new Main1(); // Create another thread that calls a proxy method. new Thread() { public void run() { Runnable proxy = (Runnable)Proxy.newProxyInstance( Proxied.class.getClassLoader(), new Class[] { Runnable.class }, new MyInvocationHandler(new Proxied())); proxy.run(); } }.start(); ensureJitCompiled(Main.class, "testOverride"); // This will create Main2 instance in the middle of testOverride(). testOverride(); } private static native void ensureJitCompiled(Class itf, String method_name); } // Put createMain2() in another class to avoid class loading due to verifier. class Helper { static Main1 createMain2() { return new Main2(); } }