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