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
17import java.lang.reflect.Method;
18
19public 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