Merge "Fix verifier checks on interface methods." into oc-mr1-dev
diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc
index 6f70b19..93d36fc 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -464,14 +464,25 @@
return FindInterfaceMethod(name, signature, pointer_size);
}
+static inline bool IsValidInheritanceCheck(ObjPtr<mirror::Class> klass,
+ ObjPtr<mirror::Class> declaring_class)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (klass->IsArrayClass()) {
+ return declaring_class->IsObjectClass();
+ } else if (klass->IsInterface()) {
+ return declaring_class->IsObjectClass() || declaring_class == klass;
+ } else {
+ return klass->IsSubClass(declaring_class);
+ }
+}
+
static inline bool IsInheritedMethod(ObjPtr<mirror::Class> klass,
ObjPtr<mirror::Class> declaring_class,
ArtMethod& method)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK_EQ(declaring_class, method.GetDeclaringClass());
DCHECK_NE(klass, declaring_class);
- DCHECK(klass->IsArrayClass() ? declaring_class->IsObjectClass()
- : klass->IsSubClass(declaring_class));
+ DCHECK(IsValidInheritanceCheck(klass, declaring_class));
uint32_t access_flags = method.GetAccessFlags();
if ((access_flags & (kAccPublic | kAccProtected)) != 0) {
return true;
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index 6dc7953..d92be17 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -3857,10 +3857,20 @@
// TODO: Maybe we should not record dependency if the invoke type does not match the lookup type.
VerifierDeps::MaybeRecordMethodResolution(*dex_file_, dex_method_idx, res_method);
+ bool must_fail = false;
+ // This is traditional and helps with screwy bytecode. It will tell you that, yes, a method
+ // exists, but that it's called incorrectly. This significantly helps debugging, as locally it's
+ // hard to see the differences.
+ // If we don't have res_method here we must fail. Just use this bool to make sure of that with a
+ // DCHECK.
if (res_method == nullptr) {
+ must_fail = true;
// Try to find the method also with the other type for better error reporting below
// but do not store such bogus lookup result in the DexCache or VerifierDeps.
if (klass->IsInterface()) {
+ // NB This is normally not really allowed but we want to get any static or private object
+ // methods for error message purposes. This will never be returned.
+ // TODO We might want to change the verifier to not require this.
res_method = klass->FindClassMethod(dex_cache_.Get(), dex_method_idx, pointer_size);
} else {
// If there was an interface method with the same signature,
@@ -3918,6 +3928,20 @@
}
}
+ // Check specifically for non-public object methods being provided for interface dispatch. This
+ // can occur if we failed to find a method with FindInterfaceMethod but later find one with
+ // FindClassMethod for error message use.
+ if (method_type == METHOD_INTERFACE &&
+ res_method->GetDeclaringClass()->IsObjectClass() &&
+ !res_method->IsPublic()) {
+ Fail(VERIFY_ERROR_NO_METHOD) << "invoke-interface " << klass->PrettyDescriptor() << "."
+ << dex_file_->GetMethodName(method_id) << " "
+ << dex_file_->GetMethodSignature(method_id) << " resolved to "
+ << "non-public object method " << res_method->PrettyMethod() << " "
+ << "but non-public Object methods are excluded from interface "
+ << "method resolution.";
+ return nullptr;
+ }
// Check if access is allowed.
if (!referrer.CanAccessMember(res_method->GetDeclaringClass(), res_method->GetAccessFlags())) {
Fail(VERIFY_ERROR_ACCESS_METHOD) << "illegal method access (call "
@@ -3946,6 +3970,13 @@
"type of " << res_method->PrettyMethod();
return nullptr;
}
+ // Make sure we weren't expecting to fail.
+ DCHECK(!must_fail) << "invoke type (" << method_type << ")"
+ << klass->PrettyDescriptor() << "."
+ << dex_file_->GetMethodName(method_id) << " "
+ << dex_file_->GetMethodSignature(method_id) << " unexpectedly resolved to "
+ << res_method->PrettyMethod() << " without error. Initially this method was "
+ << "not found so we were expecting to fail for some reason.";
return res_method;
}
diff --git a/test/075-verification-error/expected.txt b/test/075-verification-error/expected.txt
index 6e4f584..7ccc32c 100644
--- a/test/075-verification-error/expected.txt
+++ b/test/075-verification-error/expected.txt
@@ -10,3 +10,4 @@
Got expected IllegalAccessError (meth-class)
Got expected IllegalAccessError (field-class)
Got expected IllegalAccessError (meth-meth)
+Got expected IncompatibleClassChangeError (interface)
diff --git a/test/075-verification-error/src/BadIfaceImpl.java b/test/075-verification-error/src/BadIfaceImpl.java
new file mode 100644
index 0000000..fa2a970
--- /dev/null
+++ b/test/075-verification-error/src/BadIfaceImpl.java
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+public class BadIfaceImpl implements BadInterface { }
diff --git a/test/075-verification-error/src/BadInterface.java b/test/075-verification-error/src/BadInterface.java
new file mode 100644
index 0000000..439aba4
--- /dev/null
+++ b/test/075-verification-error/src/BadInterface.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+public interface BadInterface {
+ public default Object internalClone() {
+ throw new Error("Should not be called");
+ }
+}
diff --git a/test/075-verification-error/src/Main.java b/test/075-verification-error/src/Main.java
index 3f2881e..13aeaee 100644
--- a/test/075-verification-error/src/Main.java
+++ b/test/075-verification-error/src/Main.java
@@ -28,6 +28,20 @@
testClassNewInstance();
testMissingStuff();
testBadAccess();
+ testBadInterfaceMethod();
+ }
+ /**
+ * Try to create and invoke a non-existant interface method.
+ */
+ static void testBadInterfaceMethod() {
+ BadInterface badiface = new BadIfaceImpl();
+ try {
+ badiface.internalClone();
+ } catch (IncompatibleClassChangeError icce) {
+ // TODO b/64274113 This should really be an NSME
+ System.out.println("Got expected IncompatibleClassChangeError (interface)");
+ if (VERBOSE) System.out.println("--- " + icce);
+ }
}
/**
diff --git a/test/075-verification-error/src2/BadInterface.java b/test/075-verification-error/src2/BadInterface.java
new file mode 100644
index 0000000..5d939cb
--- /dev/null
+++ b/test/075-verification-error/src2/BadInterface.java
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+public interface BadInterface { }
+
diff --git a/test/162-method-resolution/expected.txt b/test/162-method-resolution/expected.txt
index 1bf39c9..9b48a4c 100644
--- a/test/162-method-resolution/expected.txt
+++ b/test/162-method-resolution/expected.txt
@@ -41,3 +41,6 @@
Calling Test9User2.test():
Caught java.lang.reflect.InvocationTargetException
caused by java.lang.IncompatibleClassChangeError
+Calling Test10User.test():
+Caught java.lang.reflect.InvocationTargetException
+ caused by java.lang.IncompatibleClassChangeError
diff --git a/test/162-method-resolution/jasmin/Test10Base.j b/test/162-method-resolution/jasmin/Test10Base.j
new file mode 100644
index 0000000..628f38d
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test10Base.j
@@ -0,0 +1,25 @@
+; 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.
+
+.class public Test10Base
+.super java/lang/Object
+.implements Test10Interface
+
+.method public <init>()V
+ .limit stack 1
+ .limit locals 1
+ aload_0
+ invokespecial java/lang/Object/<init>()V
+ return
+.end method
diff --git a/test/162-method-resolution/jasmin/Test10User.j b/test/162-method-resolution/jasmin/Test10User.j
new file mode 100644
index 0000000..6beadab
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test10User.j
@@ -0,0 +1,36 @@
+; 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.
+
+.class public Test10User
+.super java/lang/Object
+
+.method public static test()V
+ .limit stack 3
+ .limit locals 3
+ new Test10Base
+ dup
+ invokespecial Test10Base.<init>()V
+ invokestatic Test10User.doInvoke(LTest10Interface;)V
+ return
+.end method
+
+.method public static doInvoke(LTest10Interface;)V
+ .limit stack 3
+ .limit locals 3
+ aload_0
+ invokeinterface Test10Interface.clone()Ljava.lang.Object; 1
+ pop
+ return
+.end method
+
diff --git a/test/162-method-resolution/multidex.jpp b/test/162-method-resolution/multidex.jpp
index 22e3aee..5722f7f 100644
--- a/test/162-method-resolution/multidex.jpp
+++ b/test/162-method-resolution/multidex.jpp
@@ -112,6 +112,16 @@
@@com.android.jack.annotations.ForceInMainDex
class Test9User2
+Test10Base:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test10Base
+Test10Interface:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test10Interface
+Test10User:
+ @@com.android.jack.annotations.ForceInMainDex
+ class Test10User
+
Main:
@@com.android.jack.annotations.ForceInMainDex
class Main
diff --git a/test/162-method-resolution/src/Main.java b/test/162-method-resolution/src/Main.java
index fa95aa7..864c878 100644
--- a/test/162-method-resolution/src/Main.java
+++ b/test/162-method-resolution/src/Main.java
@@ -36,6 +36,7 @@
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?
@@ -376,6 +377,31 @@
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 {
diff --git a/test/162-method-resolution/src/Test10Interface.java b/test/162-method-resolution/src/Test10Interface.java
new file mode 100644
index 0000000..3c75ea5
--- /dev/null
+++ b/test/162-method-resolution/src/Test10Interface.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+public interface Test10Interface { }
+