/*
|
* 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;
|
}
|