Home | History | Annotate | Download | only in src
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 import java.lang.reflect.Method;
     18 
     19 public class Main {
     20     public static void main(String[] args) {
     21         // Check if we're running dalvik or RI.
     22         usingRI = false;
     23         try {
     24             Class.forName("dalvik.system.PathClassLoader");
     25         } catch (ClassNotFoundException e) {
     26             usingRI = true;
     27         }
     28 
     29         try {
     30             test1();
     31             test2();
     32             test3();
     33             test4();
     34             test5();
     35             test6();
     36             test7();
     37             test8();
     38             test9();
     39             test10();
     40 
     41             // TODO: How to test that interface method resolution returns the unique
     42             // maximally-specific non-abstract superinterface method if there is one?
     43             // Maybe reflection? (This is not even implemented yet!)
     44         } catch (Throwable t) {
     45             t.printStackTrace(System.out);
     46         }
     47     }
     48 
     49     /*
     50      * Test1
     51      * -----
     52      * Tested functions:
     53      *     public class Test1Base {
     54      *         public void foo() { ... }
     55      *     }
     56      *     public class Test1Derived extends Test1Base {
     57      *         private void foo() { ... }
     58      *         ...
     59      *     }
     60      * Tested invokes:
     61      *     invoke-direct  Test1Derived.foo()V   from Test1Derived in first dex file
     62      *         expected: executes Test1Derived.foo()V
     63      *     invoke-virtual Test1Derived.foo()V   from Test1User    in second dex file
     64      *         expected: throws IllegalAccessError (JLS 15.12.4.3)
     65      *     invoke-virtual Test1Derived.foo()V   from Test1User2   in first dex file
     66      *         expected: throws IllegalAccessError (JLS 15.12.4.3)
     67      *
     68      * Previously, the behavior was inconsistent between dex files, throwing ICCE
     69      * from one and invoking the method from another. This was because the lookups for
     70      * direct and virtual methods were independent but results were stored in a single
     71      * slot in the DexCache method array and then retrieved from there without checking
     72      * the resolution kind. Thus, the first invoke-direct stored the private
     73      * Test1Derived.foo() in the DexCache and the attempt to use invoke-virtual
     74      * from the same dex file (by Test1User2) would throw ICCE. However, the same
     75      * invoke-virtual from a different dex file (by Test1User) would ignore the
     76      * direct method Test1Derived.foo() and find the Test1Base.foo() and call it.
     77      *
     78      * The method lookup has been changed and we now consistently find the private
     79      * Derived.foo() and throw ICCE for both invoke-virtual calls.
     80      *
     81      * Files:
     82      *   src/Test1Base.java          - defines public foo()V.
     83      *   jasmin/Test1Derived.j       - defines private foo()V, calls it with invokespecial.
     84      *   jasmin-multidex/Test1User.j - calls invokevirtual Test1Derived.foo().
     85      *   jasmin/Test1User2.j         - calls invokevirtual Test1Derived.foo().
     86      */
     87     private static void test1() throws Exception {
     88         invokeUserTest("Test1Derived");
     89         invokeUserTest("Test1User");
     90         invokeUserTest("Test1User2");
     91     }
     92 
     93     /*
     94      * Test2
     95      * -----
     96      * Tested functions:
     97      *     public class Test2Base {
     98      *         public static void foo() { ... }
     99      *     }
    100      *     public interface Test2Interface {
    101      *         default void foo() { ... }  // default: avoid subclassing Test2Derived.
    102      *     }
    103      *     public class Test2Derived extends Test2Base implements Test2Interface {
    104      *     }
    105      * Tested invokes:
    106      *     invoke-virtual Test2Derived.foo()V   from Test2User  in first dex file
    107      *         expected: throws IncompatibleClassChangeError
    108      *                   (JLS 13.4.19, the inherited Base.foo() changed from non-static to static)
    109      *     invoke-static  Test2Derived.foo()V   from Test2User2 in first dex file
    110      *         expected: executes Test2Base.foo()V
    111      *
    112      * Previously, due to different lookup types and multi-threaded verification,
    113      * it was undeterministic which method ended up in the DexCache, so this test
    114      * was flaky, sometimes erroneously executing the Test2Interface.foo().
    115      *
    116      * The method lookup has been changed and we now consistently find the
    117      * Test2Base.foo()V over the method from the interface, in line with the RI.
    118      *
    119      * Files:
    120      *   src/Test2Base.java          - defines public static foo()V.
    121      *   src/Test2Interface.java     - defines default foo()V.
    122      *   jasmin/Test2Derived.j       - extends Test2Derived, implements Test2Interface.
    123      *   jasmin/Test2User.j          - calls invokevirtual Test2Derived.foo()
    124      *   jasmin/Test2User2.j         - calls invokestatic Test2Derived.foo()
    125      */
    126     private static void test2() throws Exception {
    127         invokeUserTest("Test2User");
    128         invokeUserTest("Test2User2");
    129     }
    130 
    131     /*
    132      * Test3
    133      * -----
    134      * Tested functions:
    135      *     public class Test3Base {
    136      *         public static void foo() { ... }
    137      *     }
    138      *     public interface Test3Interface {
    139      *         default void foo() { ... }  // default: avoid subclassing Test3Derived.
    140      *     }
    141      *     public class Test3Derived extends Test3Base implements Test3Interface {
    142      *     }
    143      * Tested invokes:
    144      *     invoke-virtual Test3Derived.foo()V   from Test3User  in second dex file
    145      *         expected: throws IncompatibleClassChangeError
    146      *                   (JLS 13.4.19, the inherited Base.foo() changed from non-static to static)
    147      *
    148      * This is Test2 (without the invoke-static) with a small change: the Test3User with
    149      * the invoke-interface is in a secondary dex file to avoid the effects of the DexCache.
    150      *
    151      * Previously the invoke-virtual would resolve to the Test3Interface.foo()V but
    152      * it now resolves to Test3Base.foo()V and throws ICCE in line with the RI.
    153      *
    154      * Files:
    155      *   src/Test3Base.java          - defines public static foo()V.
    156      *   src/Test3Interface.java     - defines default foo()V.
    157      *   src/Test3Derived.java       - extends Test2Derived, implements Test2Interface.
    158      *   jasmin-multidex/Test3User.j - calls invokevirtual Test3Derived.foo()
    159      */
    160     private static void test3() throws Exception {
    161         invokeUserTest("Test3User");
    162     }
    163 
    164     /*
    165      * Test4
    166      * -----
    167      * Tested functions:
    168      *     public interface Test4Interface {
    169      *         // Not declaring toString().
    170      *     }
    171      * Tested invokes:
    172      *     invoke-interface Test4Interface.toString()Ljava/lang/String; in first dex file
    173      *         expected: executes java.lang.Object.toString()Ljava/lang/String
    174      *                   (JLS 9.2 specifies implicitly declared methods from Object).
    175      *
    176      * The RI resolves the call to java.lang.Object.toString() and executes it.
    177      * ART used to resolve it in a secondary resolution attempt only to distinguish
    178      * between ICCE and NSME and then throw ICCE. We now allow the call to proceed.
    179      *
    180      * Files:
    181      *   src/Test4Interface.java     - does not declare toString().
    182      *   src/Test4Derived.java       - extends Test4Interface.
    183      *   jasmin/Test4User.j          - calls invokeinterface Test4Interface.toString().
    184      */
    185     private static void test4() throws Exception {
    186         invokeUserTest("Test4User");
    187     }
    188 
    189     /*
    190      * Test5
    191      * -----
    192      * Tested functions:
    193      *     public interface Test5Interface {
    194      *         public void foo();
    195      *     }
    196      *     public abstract class Test5Base implements Test5Interface{
    197      *         // Not declaring foo().
    198      *     }
    199      *     public class Test5Derived extends Test5Base {
    200      *         public void foo() { ... }
    201      *     }
    202      * Tested invokes:
    203      *     invoke-virtual   Test5Base.foo()V from Test5User  in first dex file
    204      *         expected: executes Test5Derived.foo()V
    205      *     invoke-interface Test5Base.foo()V from Test5User2 in first dex file
    206      *         expected: throws IncompatibleClassChangeError (JLS 13.3)
    207      *
    208      * We previously didn't check the type of the referencing class when the method
    209      * was found in the dex cache and the invoke-interface would only check the
    210      * type of the resolved method which happens to be OK; then we would fail a
    211      * DCHECK(!method->IsCopied()) in Class::FindVirtualMethodForInterface(). This has
    212      * been fixed and we consistently check the type of the referencing class as well.
    213      *
    214      * Since normal virtual method dispatch in compiled or quickened code does not
    215      * actually use the DexCache and we want to populate the Test5Base.foo()V entry
    216      * anyway, we force verification at runtime by adding a call to an arbitrary
    217      * unresolved method to Test5User.test(), catching and ignoring the ICCE. Files:
    218      *   src/Test5Interface.java     - interface, declares foo()V.
    219      *   src/Test5Base.java          - abstract class, implements Test5Interface.
    220      *   src/Test5Derived.java       - extends Test5Base, implements foo()V.
    221      *   jasmin/Test5User2.j         - calls invokeinterface Test5Base.foo()V.
    222      *   jasmin/Test5User.j          - calls invokevirtual Test5Base.foo()V,
    223      *                               - also calls undefined Test5Base.bar()V, supresses ICCE.
    224      */
    225     private static void test5() throws Exception {
    226         invokeUserTest("Test5User");
    227         invokeUserTest("Test5User2");
    228     }
    229 
    230     /*
    231      * Test6
    232      * -----
    233      * Tested functions:
    234      *     public interface Test6Interface {
    235      *         // Not declaring toString().
    236      *     }
    237      * Tested invokes:
    238      *     invoke-interface Test6Interface.toString() from Test6User  in first dex file
    239      *         expected: executes java.lang.Object.toString()Ljava/lang/String
    240      *                   (JLS 9.2 specifies implicitly declared methods from Object).
    241      *     invoke-virtual   Test6Interface.toString() from Test6User2 in first dex file
    242      *         expected: throws IncompatibleClassChangeError (JLS 13.3)
    243      *
    244      * Previously, the invoke-interface would have been rejected, throwing ICCE,
    245      * and the invoke-virtual would have been accepted, calling Object.toString().
    246      *
    247      * The method lookup has been changed and we now accept the invoke-interface,
    248      * calling Object.toString(), and reject the invoke-virtual, throwing ICCE,
    249      * in line with the RI. However, if the method is already in the DexCache for
    250      * the invoke-virtual, we need to check the referenced class in order to throw
    251      * the ICCE as the resolved method kind actually matches the invoke-virtual.
    252      * This test ensures that we do.
    253      *
    254      * Files:
    255      *   src/Test6Interface.java     - interface, does not declare toString().
    256      *   src/Test6Derived.java       - implements Test6Interface.
    257      *   jasmin/Test6User.j          - calls invokeinterface Test6Interface.toString().
    258      *   jasmin/Test6User2.j         - calls invokevirtual Test6Interface.toString().
    259      */
    260     private static void test6() throws Exception {
    261         invokeUserTest("Test6User");
    262         invokeUserTest("Test6User2");
    263     }
    264 
    265     /*
    266      * Test7
    267      * -----
    268      * Tested function:
    269      *     public class Test7Base {
    270      *         private void foo() { ... }
    271      *     }
    272      *     public interface Test7Interface {
    273      *         default void foo() { ... }
    274      *     }
    275      *     public class Test7Derived extends Test7Base implements Test7Interface {
    276      *         // Not declaring foo().
    277      *     }
    278      * Tested invokes:
    279      *     invoke-virtual   Test7Derived.foo()V   from Test7User in first dex file
    280      *         expected: executes Test7Interface.foo()V (inherited by Test7Derived, JLS 8.4.8)
    281      *     invoke-interface Test7Interface.foo()V from Test7User in first dex file
    282      *         expected: throws IllegalAccessError (JLS 15.12.4.4)
    283      * on a Test7Derived object.
    284      *
    285      * This tests a case where javac happily compiles code (in line with JLS) that
    286      * then throws IllegalAccessError on the RI (both invokes).
    287      *
    288      * For the invoke-virtual, the RI throws IAE as the private Test7Base.foo() is
    289      * found before the inherited (see JLS 8.4.8) Test7Interface.foo(). This conflicts
    290      * with the JLS 15.12.2.1 saying that members inherited (JLS 8.4.8) from superclasses
    291      * and superinterfaces are included in the search. ART follows the JLS behavior.
    292      *
    293      * The invoke-interface method resolution is trivial but the post-resolution
    294      * processing is non-intuitive. According to the JLS 15.12.4.4, and implemented
    295      * correctly by the RI, the invokeinterface ignores overriding and searches class
    296      * hierarchy for any method with the requested signature. Thus it finds the private
    297      * Test7Base.foo()V and throws IllegalAccessError. Unfortunately, ART does not comply
    298      * and simply calls Test7Interface.foo()V. Bug: 63624936.
    299      *
    300      * Files:
    301      *   src/Test7User.java          - calls invoke-virtual Test7Derived.foo()V.
    302      *   src/Test7Base.java          - defines private foo()V.
    303      *   src/Test7Interface.java     - defines default foo()V.
    304      *   src/Test7Derived.java       - extends Test7Base, implements Test7Interface.
    305      */
    306     private static void test7() throws Exception {
    307         if (usingRI) {
    308             // For RI, just print the expected output to hide the deliberate divergence.
    309             System.out.println("Calling Test7User.test():\n" +
    310                                "Test7Interface.foo()");
    311             invokeUserTest("Test7User2");
    312         } else {
    313             invokeUserTest("Test7User");
    314             // For ART, just print the expected output to hide the divergence. Bug: 63624936.
    315             // The expected.txt lists the desired behavior, not the current behavior.
    316             System.out.println("Calling Test7User2.test():\n" +
    317                                "Caught java.lang.reflect.InvocationTargetException\n" +
    318                                "  caused by java.lang.IllegalAccessError");
    319         }
    320     }
    321 
    322     /*
    323      * Test8
    324      * -----
    325      * Tested function:
    326      *     public class Test8Base {
    327      *         public static void foo() { ... }
    328      *     }
    329      *     public class Test8Derived extends Test8Base {
    330      *         public void foo() { ... }
    331      *     }
    332      * Tested invokes:
    333      *     invoke-virtual   Test8Derived.foo()V from Test8User in first dex file
    334      *         expected: executes Test8Derived.foo()V
    335      *     invoke-static    Test8Derived.foo()V from Test8User2 in first dex file
    336      *         expected: throws IncompatibleClassChangeError (JLS 13.4.19)
    337      *
    338      * Another test for invoke type mismatch.
    339      *
    340      * Files:
    341      *   src/Test8Base.java          - defines static foo()V.
    342      *   jasmin/Test8Derived.j       - defines non-static foo()V.
    343      *   jasmin/Test8User.j          - calls invokevirtual Test8Derived.foo()V.
    344      *   jasmin/Test8User2.j         - calls invokestatic Test8Derived.foo()V.
    345      */
    346     private static void test8() throws Exception {
    347         invokeUserTest("Test8User");
    348         invokeUserTest("Test8User2");
    349     }
    350 
    351     /*
    352      * Test9
    353      * -----
    354      * Tested function:
    355      *     public class Test9Base {
    356      *         public void foo() { ... }
    357      *     }
    358      *     public class Test9Derived extends Test9Base {
    359      *         public static void foo() { ... }
    360      *     }
    361      * Tested invokes:
    362      *     invoke-static    Test9Derived.foo()V from Test9User in first dex file
    363      *         expected: executes Test9Derived.foo()V
    364      *     invoke-virtual   Test9Derived.foo()V from Test9User2 in first dex file
    365      *         expected: throws IncompatibleClassChangeError (JLS 13.4.19)
    366      *
    367      * Another test for invoke type mismatch.
    368      *
    369      * Files:
    370      *   src/Test9Base.java          - defines non-static foo()V.
    371      *   jasmin/Test9Derived.j       - defines static foo()V.
    372      *   jasmin/Test9User.j          - calls invokestatic Test8Derived.foo()V.
    373      *   jasmin/Test9User2.j         - calls invokevirtual Test8Derived.foo()V.
    374      */
    375     private static void test9() throws Exception {
    376         invokeUserTest("Test9User");
    377         invokeUserTest("Test9User2");
    378     }
    379 
    380     /*
    381      * Test10
    382      * -----
    383      * Tested function:
    384      *     public class Test10Base implements Test10Interface { }
    385      *     public interface Test10Interface { }
    386      * Tested invokes:
    387      *     invoke-interface Test10Interface.clone()Ljava/lang/Object; from Test10Caller in first dex
    388      *         TODO b/64274113 This should throw a NSME (JLS 13.4.12) but actually throws an ICCE.
    389      *         expected: Throws NoSuchMethodError (JLS 13.4.12)
    390      *         actual: Throws IncompatibleClassChangeError
    391      *
    392      * This test is simulating compiling Test10Interface with "public Object clone()" method, along
    393      * with every other class. Then we delete "clone" from Test10Interface only, which under JLS
    394      * 13.4.12 is expected to be binary incompatible and throw a NoSuchMethodError.
    395      *
    396      * Files:
    397      *   jasmin/Test10Base.j          - implements Test10Interface
    398      *   jasmin/Test10Interface.java  - defines empty interface
    399      *   jasmin/Test10User.j          - invokeinterface Test10Interface.clone()Ljava/lang/Object;
    400      */
    401     private static void test10() throws Exception {
    402         invokeUserTest("Test10User");
    403     }
    404 
    405     private static void invokeUserTest(String userName) throws Exception {
    406         System.out.println("Calling " + userName + ".test():");
    407         try {
    408             Class<?> user = Class.forName(userName);
    409             Method utest = user.getDeclaredMethod("test");
    410             utest.invoke(null);
    411         } catch (Throwable t) {
    412             System.out.println("Caught " + t.getClass().getName());
    413             for (Throwable c = t.getCause(); c != null; c = c.getCause()) {
    414                 System.out.println("  caused by " + c.getClass().getName());
    415             }
    416         }
    417     }
    418 
    419     // Replace the variable part of the output of the default toString() implementation
    420     // so that we have a deterministic output.
    421     static String normalizeToString(String s) {
    422         int atPos = s.indexOf("@");
    423         return s.substring(0, atPos + 1) + "...";
    424     }
    425 
    426     static boolean usingRI;
    427 }
    428