| /* |
| * 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. |
| */ |
| |
| import java.lang.reflect.Method; |
| |
| public class Main { |
| public static void main(String[] args) { |
| // Check if we're running dalvik or RI. |
| usingRI = false; |
| try { |
| Class.forName("dalvik.system.PathClassLoader"); |
| } catch (ClassNotFoundException e) { |
| usingRI = true; |
| } |
| |
| try { |
| test1(); |
| test2(); |
| test3(); |
| test4(); |
| test5(); |
| test6(); |
| test7(); |
| test8(); |
| test9(); |
| test10(); |
| |
| // TODO: How to test that interface method resolution returns the unique |
| // maximally-specific non-abstract superinterface method if there is one? |
| // Maybe reflection? (This is not even implemented yet!) |
| } catch (Throwable t) { |
| t.printStackTrace(System.out); |
| } |
| } |
| |
| /* |
| * Test1 |
| * ----- |
| * Tested functions: |
| * public class Test1Base { |
| * public void foo() { ... } |
| * } |
| * public class Test1Derived extends Test1Base { |
| * private void foo() { ... } |
| * ... |
| * } |
| * Tested invokes: |
| * invoke-direct Test1Derived.foo()V from Test1Derived in first dex file |
| * expected: executes Test1Derived.foo()V |
| * invoke-virtual Test1Derived.foo()V from Test1User in second dex file |
| * expected: throws IllegalAccessError (JLS 15.12.4.3) |
| * invoke-virtual Test1Derived.foo()V from Test1User2 in first dex file |
| * expected: throws IllegalAccessError (JLS 15.12.4.3) |
| * |
| * Previously, the behavior was inconsistent between dex files, throwing ICCE |
| * from one and invoking the method from another. This was because the lookups for |
| * direct and virtual methods were independent but results were stored in a single |
| * slot in the DexCache method array and then retrieved from there without checking |
| * the resolution kind. Thus, the first invoke-direct stored the private |
| * Test1Derived.foo() in the DexCache and the attempt to use invoke-virtual |
| * from the same dex file (by Test1User2) would throw ICCE. However, the same |
| * invoke-virtual from a different dex file (by Test1User) would ignore the |
| * direct method Test1Derived.foo() and find the Test1Base.foo() and call it. |
| * |
| * The method lookup has been changed and we now consistently find the private |
| * Derived.foo() and throw ICCE for both invoke-virtual calls. |
| * |
| * Files: |
| * src/Test1Base.java - defines public foo()V. |
| * jasmin/Test1Derived.j - defines private foo()V, calls it with invokespecial. |
| * jasmin-multidex/Test1User.j - calls invokevirtual Test1Derived.foo(). |
| * jasmin/Test1User2.j - calls invokevirtual Test1Derived.foo(). |
| */ |
| private static void test1() throws Exception { |
| invokeUserTest("Test1Derived"); |
| invokeUserTest("Test1User"); |
| invokeUserTest("Test1User2"); |
| } |
| |
| /* |
| * Test2 |
| * ----- |
| * Tested functions: |
| * public class Test2Base { |
| * public static void foo() { ... } |
| * } |
| * public interface Test2Interface { |
| * default void foo() { ... } // default: avoid subclassing Test2Derived. |
| * } |
| * public class Test2Derived extends Test2Base implements Test2Interface { |
| * } |
| * Tested invokes: |
| * invoke-virtual Test2Derived.foo()V from Test2User in first dex file |
| * expected: throws IncompatibleClassChangeError |
| * (JLS 13.4.19, the inherited Base.foo() changed from non-static to static) |
| * invoke-static Test2Derived.foo()V from Test2User2 in first dex file |
| * expected: executes Test2Base.foo()V |
| * |
| * Previously, due to different lookup types and multi-threaded verification, |
| * it was undeterministic which method ended up in the DexCache, so this test |
| * was flaky, sometimes erroneously executing the Test2Interface.foo(). |
| * |
| * The method lookup has been changed and we now consistently find the |
| * Test2Base.foo()V over the method from the interface, in line with the RI. |
| * |
| * Files: |
| * src/Test2Base.java - defines public static foo()V. |
| * src/Test2Interface.java - defines default foo()V. |
| * jasmin/Test2Derived.j - extends Test2Derived, implements Test2Interface. |
| * jasmin/Test2User.j - calls invokevirtual Test2Derived.foo() |
| * jasmin/Test2User2.j - calls invokestatic Test2Derived.foo() |
| */ |
| private static void test2() throws Exception { |
| invokeUserTest("Test2User"); |
| invokeUserTest("Test2User2"); |
| } |
| |
| /* |
| * Test3 |
| * ----- |
| * Tested functions: |
| * public class Test3Base { |
| * public static void foo() { ... } |
| * } |
| * public interface Test3Interface { |
| * default void foo() { ... } // default: avoid subclassing Test3Derived. |
| * } |
| * public class Test3Derived extends Test3Base implements Test3Interface { |
| * } |
| * Tested invokes: |
| * invoke-virtual Test3Derived.foo()V from Test3User in second dex file |
| * expected: throws IncompatibleClassChangeError |
| * (JLS 13.4.19, the inherited Base.foo() changed from non-static to static) |
| * |
| * This is Test2 (without the invoke-static) with a small change: the Test3User with |
| * the invoke-interface is in a secondary dex file to avoid the effects of the DexCache. |
| * |
| * Previously the invoke-virtual would resolve to the Test3Interface.foo()V but |
| * it now resolves to Test3Base.foo()V and throws ICCE in line with the RI. |
| * |
| * Files: |
| * src/Test3Base.java - defines public static foo()V. |
| * src/Test3Interface.java - defines default foo()V. |
| * src/Test3Derived.java - extends Test2Derived, implements Test2Interface. |
| * jasmin-multidex/Test3User.j - calls invokevirtual Test3Derived.foo() |
| */ |
| private static void test3() throws Exception { |
| invokeUserTest("Test3User"); |
| } |
| |
| /* |
| * Test4 |
| * ----- |
| * Tested functions: |
| * public interface Test4Interface { |
| * // Not declaring toString(). |
| * } |
| * Tested invokes: |
| * invoke-interface Test4Interface.toString()Ljava/lang/String; in first dex file |
| * expected: executes java.lang.Object.toString()Ljava/lang/String |
| * (JLS 9.2 specifies implicitly declared methods from Object). |
| * |
| * The RI resolves the call to java.lang.Object.toString() and executes it. |
| * ART used to resolve it in a secondary resolution attempt only to distinguish |
| * between ICCE and NSME and then throw ICCE. We now allow the call to proceed. |
| * |
| * Files: |
| * src/Test4Interface.java - does not declare toString(). |
| * src/Test4Derived.java - extends Test4Interface. |
| * jasmin/Test4User.j - calls invokeinterface Test4Interface.toString(). |
| */ |
| private static void test4() throws Exception { |
| invokeUserTest("Test4User"); |
| } |
| |
| /* |
| * Test5 |
| * ----- |
| * Tested functions: |
| * public interface Test5Interface { |
| * public void foo(); |
| * } |
| * public abstract class Test5Base implements Test5Interface{ |
| * // Not declaring foo(). |
| * } |
| * public class Test5Derived extends Test5Base { |
| * public void foo() { ... } |
| * } |
| * Tested invokes: |
| * invoke-virtual Test5Base.foo()V from Test5User in first dex file |
| * expected: executes Test5Derived.foo()V |
| * invoke-interface Test5Base.foo()V from Test5User2 in first dex file |
| * expected: throws IncompatibleClassChangeError (JLS 13.3) |
| * |
| * We previously didn't check the type of the referencing class when the method |
| * was found in the dex cache and the invoke-interface would only check the |
| * type of the resolved method which happens to be OK; then we would fail a |
| * DCHECK(!method->IsCopied()) in Class::FindVirtualMethodForInterface(). This has |
| * been fixed and we consistently check the type of the referencing class as well. |
| * |
| * Since normal virtual method dispatch in compiled or quickened code does not |
| * actually use the DexCache and we want to populate the Test5Base.foo()V entry |
| * anyway, we force verification at runtime by adding a call to an arbitrary |
| * unresolved method to Test5User.test(), catching and ignoring the ICCE. Files: |
| * src/Test5Interface.java - interface, declares foo()V. |
| * src/Test5Base.java - abstract class, implements Test5Interface. |
| * src/Test5Derived.java - extends Test5Base, implements foo()V. |
| * jasmin/Test5User2.j - calls invokeinterface Test5Base.foo()V. |
| * jasmin/Test5User.j - calls invokevirtual Test5Base.foo()V, |
| * - also calls undefined Test5Base.bar()V, supresses ICCE. |
| */ |
| private static void test5() throws Exception { |
| invokeUserTest("Test5User"); |
| invokeUserTest("Test5User2"); |
| } |
| |
| /* |
| * Test6 |
| * ----- |
| * Tested functions: |
| * public interface Test6Interface { |
| * // Not declaring toString(). |
| * } |
| * Tested invokes: |
| * invoke-interface Test6Interface.toString() from Test6User in first dex file |
| * expected: executes java.lang.Object.toString()Ljava/lang/String |
| * (JLS 9.2 specifies implicitly declared methods from Object). |
| * invoke-virtual Test6Interface.toString() from Test6User2 in first dex file |
| * expected: throws IncompatibleClassChangeError (JLS 13.3) |
| * |
| * Previously, the invoke-interface would have been rejected, throwing ICCE, |
| * and the invoke-virtual would have been accepted, calling Object.toString(). |
| * |
| * The method lookup has been changed and we now accept the invoke-interface, |
| * calling Object.toString(), and reject the invoke-virtual, throwing ICCE, |
| * in line with the RI. However, if the method is already in the DexCache for |
| * the invoke-virtual, we need to check the referenced class in order to throw |
| * the ICCE as the resolved method kind actually matches the invoke-virtual. |
| * This test ensures that we do. |
| * |
| * Files: |
| * src/Test6Interface.java - interface, does not declare toString(). |
| * src/Test6Derived.java - implements Test6Interface. |
| * jasmin/Test6User.j - calls invokeinterface Test6Interface.toString(). |
| * jasmin/Test6User2.j - calls invokevirtual Test6Interface.toString(). |
| */ |
| private static void test6() throws Exception { |
| invokeUserTest("Test6User"); |
| invokeUserTest("Test6User2"); |
| } |
| |
| /* |
| * Test7 |
| * ----- |
| * Tested function: |
| * public class Test7Base { |
| * private void foo() { ... } |
| * } |
| * public interface Test7Interface { |
| * default void foo() { ... } |
| * } |
| * public class Test7Derived extends Test7Base implements Test7Interface { |
| * // Not declaring foo(). |
| * } |
| * Tested invokes: |
| * invoke-virtual Test7Derived.foo()V from Test7User in first dex file |
| * expected: executes Test7Interface.foo()V (inherited by Test7Derived, JLS 8.4.8) |
| * invoke-interface Test7Interface.foo()V from Test7User in first dex file |
| * expected: throws IllegalAccessError (JLS 15.12.4.4) |
| * on a Test7Derived object. |
| * |
| * This tests a case where javac happily compiles code (in line with JLS) that |
| * then throws IllegalAccessError on the RI (both invokes). |
| * |
| * For the invoke-virtual, the RI throws IAE as the private Test7Base.foo() is |
| * found before the inherited (see JLS 8.4.8) Test7Interface.foo(). This conflicts |
| * with the JLS 15.12.2.1 saying that members inherited (JLS 8.4.8) from superclasses |
| * and superinterfaces are included in the search. ART follows the JLS behavior. |
| * |
| * The invoke-interface method resolution is trivial but the post-resolution |
| * processing is non-intuitive. According to the JLS 15.12.4.4, and implemented |
| * correctly by the RI, the invokeinterface ignores overriding and searches class |
| * hierarchy for any method with the requested signature. Thus it finds the private |
| * Test7Base.foo()V and throws IllegalAccessError. Unfortunately, ART does not comply |
| * and simply calls Test7Interface.foo()V. Bug: 63624936. |
| * |
| * Files: |
| * src/Test7User.java - calls invoke-virtual Test7Derived.foo()V. |
| * src/Test7Base.java - defines private foo()V. |
| * src/Test7Interface.java - defines default foo()V. |
| * src/Test7Derived.java - extends Test7Base, implements Test7Interface. |
| */ |
| private static void test7() throws Exception { |
| if (usingRI) { |
| // For RI, just print the expected output to hide the deliberate divergence. |
| System.out.println("Calling Test7User.test():\n" + |
| "Test7Interface.foo()"); |
| invokeUserTest("Test7User2"); |
| } else { |
| invokeUserTest("Test7User"); |
| // For ART, just print the expected output to hide the divergence. Bug: 63624936. |
| // The expected.txt lists the desired behavior, not the current behavior. |
| System.out.println("Calling Test7User2.test():\n" + |
| "Caught java.lang.reflect.InvocationTargetException\n" + |
| " caused by java.lang.IllegalAccessError"); |
| } |
| } |
| |
| /* |
| * Test8 |
| * ----- |
| * Tested function: |
| * public class Test8Base { |
| * public static void foo() { ... } |
| * } |
| * public class Test8Derived extends Test8Base { |
| * public void foo() { ... } |
| * } |
| * Tested invokes: |
| * invoke-virtual Test8Derived.foo()V from Test8User in first dex file |
| * expected: executes Test8Derived.foo()V |
| * invoke-static Test8Derived.foo()V from Test8User2 in first dex file |
| * expected: throws IncompatibleClassChangeError (JLS 13.4.19) |
| * |
| * Another test for invoke type mismatch. |
| * |
| * Files: |
| * src/Test8Base.java - defines static foo()V. |
| * jasmin/Test8Derived.j - defines non-static foo()V. |
| * jasmin/Test8User.j - calls invokevirtual Test8Derived.foo()V. |
| * jasmin/Test8User2.j - calls invokestatic Test8Derived.foo()V. |
| */ |
| private static void test8() throws Exception { |
| invokeUserTest("Test8User"); |
| invokeUserTest("Test8User2"); |
| } |
| |
| /* |
| * Test9 |
| * ----- |
| * Tested function: |
| * public class Test9Base { |
| * public void foo() { ... } |
| * } |
| * public class Test9Derived extends Test9Base { |
| * public static void foo() { ... } |
| * } |
| * Tested invokes: |
| * invoke-static Test9Derived.foo()V from Test9User in first dex file |
| * expected: executes Test9Derived.foo()V |
| * invoke-virtual Test9Derived.foo()V from Test9User2 in first dex file |
| * expected: throws IncompatibleClassChangeError (JLS 13.4.19) |
| * |
| * Another test for invoke type mismatch. |
| * |
| * Files: |
| * src/Test9Base.java - defines non-static foo()V. |
| * jasmin/Test9Derived.j - defines static foo()V. |
| * jasmin/Test9User.j - calls invokestatic Test8Derived.foo()V. |
| * jasmin/Test9User2.j - calls invokevirtual Test8Derived.foo()V. |
| */ |
| private static void test9() throws Exception { |
| invokeUserTest("Test9User"); |
| invokeUserTest("Test9User2"); |
| } |
| |
| /* |
| * Test10 |
| * ----- |
| * Tested function: |
| * public class Test10Base implements Test10Interface { } |
| * public interface Test10Interface { } |
| * Tested invokes: |
| * invoke-interface Test10Interface.clone()Ljava/lang/Object; from Test10Caller in first dex |
| * TODO b/64274113 This should throw a NSME (JLS 13.4.12) but actually throws an ICCE. |
| * expected: Throws NoSuchMethodError (JLS 13.4.12) |
| * actual: Throws IncompatibleClassChangeError |
| * |
| * This test is simulating compiling Test10Interface with "public Object clone()" method, along |
| * with every other class. Then we delete "clone" from Test10Interface only, which under JLS |
| * 13.4.12 is expected to be binary incompatible and throw a NoSuchMethodError. |
| * |
| * Files: |
| * jasmin/Test10Base.j - implements Test10Interface |
| * jasmin/Test10Interface.java - defines empty interface |
| * jasmin/Test10User.j - invokeinterface Test10Interface.clone()Ljava/lang/Object; |
| */ |
| private static void test10() throws Exception { |
| invokeUserTest("Test10User"); |
| } |
| |
| private static void invokeUserTest(String userName) throws Exception { |
| System.out.println("Calling " + userName + ".test():"); |
| try { |
| Class<?> user = Class.forName(userName); |
| Method utest = user.getDeclaredMethod("test"); |
| utest.invoke(null); |
| } catch (Throwable t) { |
| System.out.println("Caught " + t.getClass().getName()); |
| for (Throwable c = t.getCause(); c != null; c = c.getCause()) { |
| System.out.println(" caused by " + c.getClass().getName()); |
| } |
| } |
| } |
| |
| // Replace the variable part of the output of the default toString() implementation |
| // so that we have a deterministic output. |
| static String normalizeToString(String s) { |
| int atPos = s.indexOf("@"); |
| return s.substring(0, atPos + 1) + "..."; |
| } |
| |
| static boolean usingRI; |
| } |