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