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