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