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