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 
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      */
test1()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      */
test2()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      */
test3()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      */
test4()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      */
test5()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      */
test6()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      */
test7()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      */
test8()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      */
test9()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      */
test10()401     private static void test10() throws Exception {
402         invokeUserTest("Test10User");
403     }
404 
invokeUserTest(String userName)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.
normalizeToString(String s)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