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