Canonicalize non default conflicting methods in jni::EncodeArtMethod
jmethodIDs returned by jni::EncodeArtMethod are used by external
interfaces (for ex: jvmti) to match methods. As an example, when single
stepping jvmti returns method id corresponding to the methods on the
stack. To map these method ids to method name / signature the method ids
are matched against the method ids returned by the GetDeclaredMethods of
the class. This generally works but breaks for copied methods. So we
should canonicalize methods before returning the method id. We already
do this when using opaque jni ids.
We shouldn't canonicalize default conflicting methods. Canonicalization
returns one of the interface methods. If this method id is used to call
the method via the CallNonVirtualMethod jni interface, the method
executes the interface method instead of throwing ICCE error.
This CL also fixes this for opaque jni ids to not canonicalize default
conflict methods.This also adds a few comments for the opaque jni ids.
Bug: 244683447
Test: art/testrunner.py -t 2243-step-default-method, 2262*
Change-Id: Ie2e9225fb8ebf5235f08345a079eb73f28f33961
diff --git a/runtime/jni/jni_id_manager.cc b/runtime/jni/jni_id_manager.cc
index 402259b..a668fe5 100644
--- a/runtime/jni/jni_id_manager.cc
+++ b/runtime/jni/jni_id_manager.cc
@@ -64,6 +64,17 @@
return (index << 1) + 1;
}
+static bool CanUseIdArrays(ArtMethod* t) {
+ // We cannot use ID arrays from the ClassExt object for obsolete and default conflict methods. The
+ // ID arrays hold an ID corresponding to the methods in the methods_list. Obsolete methods aren't
+ // in the method list. For default conflicting methods it is difficult to find the class that
+ // contains the copied method, so we omit using ID arrays. For Default conflicting methods we
+ // cannot use the canonical method because canonicalizing would return a method from one of the
+ // interface classes. If we use that method ID and invoke it via the CallNonVirtual JNI interface,
+ // it wouldn't throw the expected ICCE.
+ return !(t->IsObsolete() || t->IsDefaultConflicting());
+}
+
template <typename ArtType>
ObjPtr<mirror::PointerArray> GetIds(ObjPtr<mirror::Class> k, ArtType* t)
REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -71,7 +82,7 @@
if constexpr (std::is_same_v<ArtType, ArtField>) {
ret = t->IsStatic() ? k->GetStaticFieldIds() : k->GetInstanceFieldIds();
} else {
- ret = t->IsObsolete() ? nullptr : k->GetMethodIds();
+ ret = CanUseIdArrays(t) ? k->GetMethodIds() : nullptr;
}
DCHECK(ret.IsNull() || ret->IsArrayInstance()) << "Should have bailed out early!";
if (kIsDebugBuild && !ret.IsNull()) {
@@ -138,9 +149,10 @@
template <>
bool EnsureIdsArray(Thread* self, ObjPtr<mirror::Class> k, ArtMethod* method) {
- if (method->IsObsolete()) {
+ if (!CanUseIdArrays(method)) {
if (kTraceIds) {
- LOG(INFO) << "jmethodID for Obsolete method " << method->PrettyMethod() << " requested!";
+ LOG(INFO) << "jmethodID for Obsolete / Default conflicting method " << method->PrettyMethod()
+ << " requested!";
}
// No ids array for obsolete methods. Just do a linear scan.
return false;
@@ -169,7 +181,7 @@
}
template <>
size_t GetIdOffset(ObjPtr<mirror::Class> k, ArtMethod* method, PointerSize pointer_size) {
- return method->IsObsolete() ? -1 : k->GetMethodIdOffset(method, pointer_size);
+ return CanUseIdArrays(method) ? k->GetMethodIdOffset(method, pointer_size) : -1;
}
// Calls the relevant PrettyMethod/PrettyField on the input.
@@ -192,16 +204,16 @@
return f->PrettyField();
}
-// Checks if the field or method is obsolete.
+// Checks if the field or method can use the ID array from class extension.
template <typename ArtType>
-bool IsObsolete(ReflectiveHandle<ArtType> t) REQUIRES_SHARED(Locks::mutator_lock_);
+bool CanUseIdArrays(ReflectiveHandle<ArtType> t) REQUIRES_SHARED(Locks::mutator_lock_);
template <>
-bool IsObsolete(ReflectiveHandle<ArtField> t ATTRIBUTE_UNUSED) {
- return false;
+bool CanUseIdArrays(ReflectiveHandle<ArtField> t ATTRIBUTE_UNUSED) {
+ return true;
}
template <>
-bool IsObsolete(ReflectiveHandle<ArtMethod> t) {
- return t->IsObsolete();
+bool CanUseIdArrays(ReflectiveHandle<ArtMethod> t) {
+ return CanUseIdArrays(t.Get());
}
// Get the canonical (non-copied) version of the field or method. Only relevant for methods.
@@ -258,10 +270,14 @@
template <>
size_t JniIdManager::GetLinearSearchStartId<ArtMethod>(ReflectiveHandle<ArtMethod> m) {
- if (m->IsObsolete()) {
- return 1;
- } else {
+ if (CanUseIdArrays(m)) {
+ // If we are searching because we couldn't allocate because of defer allocate scope, then we
+ // should only look from deferred_allocation_method_id_start_. Once we exit the deferred scope
+ // all these method ids will be updated to the id arrays in the respective ClassExt objects.
return deferred_allocation_method_id_start_;
+ } else {
+ // If we cannot use ID arrays, then the method can be anywhere in the list.
+ return 1;
}
}
@@ -278,14 +294,26 @@
Thread* self = Thread::Current();
ScopedExceptionStorage ses(self);
DCHECK(!t->GetDeclaringClass().IsNull()) << "Null declaring class " << PrettyGeneric(t);
- size_t off = GetIdOffset(t->GetDeclaringClass(), Canonicalize(t), kRuntimePointerSize);
- // Here is the earliest point we can suspend.
- bool allocation_failure = EnsureIdsArray(self, t->GetDeclaringClass(), t.Get());
+ size_t off = -1;
+ bool allocation_failure = false;
+ // When we cannot use ID arrays, we just fallback to looking through the list to obtain the ID.
+ // These are rare cases so shouldn't be a problem for performance. See CanUseIdArrays for more
+ // information.
+ if (CanUseIdArrays(t)) {
+ off = GetIdOffset(t->GetDeclaringClass(), Canonicalize(t), kRuntimePointerSize);
+ // Here is the earliest point we can suspend.
+ allocation_failure = EnsureIdsArray(self, t->GetDeclaringClass(), t.Get());
+ }
if (allocation_failure) {
self->AssertPendingOOMException();
ses.SuppressOldException("OOM exception while trying to allocate JNI ids.");
return 0u;
} else if (ShouldReturnPointer(t->GetDeclaringClass(), t.Get())) {
+ // TODO(mythria): Check why we return a pointer here instead of falling back
+ // to the slow path of finding the ID by looping through the ID -> method
+ // map. This seem incorrect. For example, if we are in ScopedEnableSuspendAllJniIdQueries
+ // scope, we don't allocate ID arrays. We would then incorrectly return a
+ // pointer here.
return reinterpret_cast<uintptr_t>(t.Get());
}
ObjPtr<mirror::Class> klass = t->GetDeclaringClass();
@@ -321,7 +349,7 @@
}
} else {
// We cannot allocate anything here or don't have an ids array (we might be an obsolete method).
- DCHECK(IsObsolete(t) || deferred_allocation_refcount_ > 0u)
+ DCHECK(!CanUseIdArrays(t) || deferred_allocation_refcount_ > 0u)
<< "deferred_allocation_refcount_: " << deferred_allocation_refcount_
<< " t: " << PrettyGeneric(t);
// Check to see if we raced and lost to another thread.
@@ -354,7 +382,7 @@
vec.resize(std::max(vec.size(), cur_index + 1), nullptr);
vec[cur_index] = t.Get();
if (ids.IsNull()) {
- if (kIsDebugBuild && !IsObsolete(t)) {
+ if (kIsDebugBuild && CanUseIdArrays(t)) {
CHECK_NE(deferred_allocation_refcount_, 0u)
<< "Failed to allocate ids array despite not being forbidden from doing so!";
Locks::mutator_lock_->AssertExclusiveHeld(self);
diff --git a/runtime/jni/jni_internal.h b/runtime/jni/jni_internal.h
index 1616ee5..cfe8208 100644
--- a/runtime/jni/jni_internal.h
+++ b/runtime/jni/jni_internal.h
@@ -115,8 +115,12 @@
REQUIRES_SHARED(Locks::mutator_lock_) {
if (kEnableIndexIds && Runtime::Current()->GetJniIdType() != JniIdType::kPointer) {
return Runtime::Current()->GetJniIdManager()->EncodeMethodId(art_method);
- } else {
+ } else if (art_method == nullptr ||
+ !art_method->IsCopied() ||
+ art_method->IsDefaultConflicting()) {
return reinterpret_cast<jmethodID>(art_method.Get());
+ } else {
+ return reinterpret_cast<jmethodID>(art_method->GetCanonicalMethod());
}
}
@@ -126,8 +130,12 @@
REQUIRES_SHARED(Locks::mutator_lock_) {
if (kEnableIndexIds && Runtime::Current()->GetJniIdType() != JniIdType::kPointer) {
return Runtime::Current()->GetJniIdManager()->EncodeMethodId(art_method);
- } else {
+ } else if (art_method == nullptr ||
+ !art_method->IsCopied() ||
+ art_method->IsDefaultConflicting()) {
return reinterpret_cast<jmethodID>(art_method);
+ } else {
+ return reinterpret_cast<jmethodID>(art_method->GetCanonicalMethod());
}
}
diff --git a/test/2243-single-step-default/Android.bp b/test/2243-single-step-default/Android.bp
new file mode 100644
index 0000000..d7d1b2f
--- /dev/null
+++ b/test/2243-single-step-default/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2243-single-step-default`.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "art_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+ name: "art-run-test-2243-single-step-default",
+ defaults: ["art-run-test-defaults"],
+ test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+ srcs: ["src/**/*.java"],
+ data: [
+ ":art-run-test-2243-single-step-default-expected-stdout",
+ ":art-run-test-2243-single-step-default-expected-stderr",
+ ],
+}
+
+// Test's expected standard output.
+genrule {
+ name: "art-run-test-2243-single-step-default-expected-stdout",
+ out: ["art-run-test-2243-single-step-default-expected-stdout.txt"],
+ srcs: ["expected-stdout.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+ name: "art-run-test-2243-single-step-default-expected-stderr",
+ out: ["art-run-test-2243-single-step-default-expected-stderr.txt"],
+ srcs: ["expected-stderr.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/2243-single-step-default/expected-stderr.txt b/test/2243-single-step-default/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2243-single-step-default/expected-stderr.txt
diff --git a/test/2243-single-step-default/expected-stdout.txt b/test/2243-single-step-default/expected-stdout.txt
new file mode 100644
index 0000000..3a1c1bd
--- /dev/null
+++ b/test/2243-single-step-default/expected-stdout.txt
@@ -0,0 +1 @@
+art.Test2243$DefaultImpl::doSomething
diff --git a/test/2243-single-step-default/info.txt b/test/2243-single-step-default/info.txt
new file mode 100644
index 0000000..ada764b
--- /dev/null
+++ b/test/2243-single-step-default/info.txt
@@ -0,0 +1 @@
+Tests that the jmethodIDs are always for canonicalized methods.
diff --git a/test/2243-single-step-default/run.py b/test/2243-single-step-default/run.py
new file mode 100755
index 0000000..832c074
--- /dev/null
+++ b/test/2243-single-step-default/run.py
@@ -0,0 +1,18 @@
+# Copyright 2022 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.
+
+
+def run(ctx, args):
+ # Ask for stack traces to be dumped to a file rather than to stdout.
+ ctx.default_run(args, jvmti=True)
diff --git a/test/2243-single-step-default/single_step_helper.cc b/test/2243-single-step-default/single_step_helper.cc
new file mode 100644
index 0000000..432e982
--- /dev/null
+++ b/test/2243-single-step-default/single_step_helper.cc
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "android-base/macros.h"
+#include "jni.h"
+#include "jvmti.h"
+#include "jvmti_helper.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test2243SingleStepDefault {
+
+jmethodID interface_default_method;
+
+static void singleStepCB(jvmtiEnv* jvmti,
+ JNIEnv* env,
+ jthread thr,
+ jmethodID method,
+ jlocation location ATTRIBUTE_UNUSED) {
+ // We haven't reached the default method yet. Continue single stepping
+ if (method != interface_default_method) {
+ return;
+ }
+
+ // Disable single stepping
+ jvmtiError err = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_SINGLE_STEP, thr);
+ if (JvmtiErrorToException(env, jvmti_env, err)) {
+ return;
+ }
+
+ // Inspect the frame.
+ jint frame_count;
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetFrameCount(thr, &frame_count))) {
+ return;
+ }
+ CHECK_GT(frame_count, 0);
+
+ // Check that the method id from the stack frame is same as the one returned
+ // by single step callback
+ jmethodID m = nullptr;
+ jlong loc = -1;
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetFrameLocation(thr, 0, &m, &loc))) {
+ return;
+ }
+ CHECK_EQ(m, method) << "Method id from stack walk doesn't match id from single step callback";
+
+ // Check that the method id is also present in the declaring class
+ jclass klass = nullptr;
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetMethodDeclaringClass(m, &klass))) {
+ return;
+ }
+ jint count = 0;
+ jmethodID* methods = nullptr;
+ jvmtiError result = jvmti_env->GetClassMethods(klass, &count, &methods);
+ if (JvmtiErrorToException(env, jvmti_env, result)) {
+ return;
+ }
+
+ bool found_method_id = false;
+ for (int i = 0; i < count; i++) {
+ if (methods[i] == method) {
+ found_method_id = true;
+ break;
+ }
+ }
+ CHECK(found_method_id) << "Couldn't find the method id in the declaring class";
+
+ // Check it isn't copied method.
+ jint access_flags = 0;
+ if (JvmtiErrorToException(env, jvmti, jvmti->GetMethodModifiers(m, &access_flags))) {
+ return;
+ }
+ static constexpr uint32_t kAccCopied = 0x01000000;
+ static constexpr uint32_t kAccIntrinsic = 0x80000000;
+ bool is_copied = ((access_flags & (kAccIntrinsic | kAccCopied)) == kAccCopied);
+ CHECK(!is_copied) << "Got copied methodID. Missed canonicalizing?\n";
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test2243_setSingleStepCallback(JNIEnv* env) {
+ jvmtiEventCallbacks callbacks;
+ memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
+ callbacks.SingleStep = singleStepCB;
+
+ jvmtiError ret = jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks));
+ JvmtiErrorToException(env, jvmti_env, ret);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test2243_enableSingleStep(JNIEnv* env,
+ jclass ATTRIBUTE_UNUSED,
+ jthread thr) {
+ jvmtiError err = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_SINGLE_STEP, thr);
+ JvmtiErrorToException(env, jvmti_env, err);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test2243_setSingleStepUntil(JNIEnv* env,
+ jclass cl ATTRIBUTE_UNUSED,
+ jobject method) {
+ interface_default_method = env->FromReflectedMethod(method);
+}
+
+} // namespace Test2243SingleStepDefault
+} // namespace art
diff --git a/test/2243-single-step-default/src/Main.java b/test/2243-single-step-default/src/Main.java
new file mode 100644
index 0000000..222fe49
--- /dev/null
+++ b/test/2243-single-step-default/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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 Main {
+ public static void main(String[] args) throws Exception {
+ art.Test2243.run();
+ }
+}
diff --git a/test/2243-single-step-default/src/art/InterfaceWithDefaultMethods.java b/test/2243-single-step-default/src/art/InterfaceWithDefaultMethods.java
new file mode 100644
index 0000000..8c6fc91
--- /dev/null
+++ b/test/2243-single-step-default/src/art/InterfaceWithDefaultMethods.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package art;
+
+interface InterfaceWithDefaultMethods {
+ default void doSomething() {
+ String name = getClass().getName();
+ System.out.println(name + "::doSomething");
+ }
+}
diff --git a/test/2243-single-step-default/src/art/Test2243.java b/test/2243-single-step-default/src/art/Test2243.java
new file mode 100644
index 0000000..68bdcd1
--- /dev/null
+++ b/test/2243-single-step-default/src/art/Test2243.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+public class Test2243 {
+ static Method default_method;
+ static class DefaultImpl implements InterfaceWithDefaultMethods {}
+
+ public static void testDefaultMethod(InterfaceWithDefaultMethods i) {
+ enableSingleStep(Thread.currentThread());
+ i.doSomething();
+ }
+
+ public static void run() throws Exception {
+ setSingleStepCallback();
+ setSingleStepUntil(InterfaceWithDefaultMethods.class.getDeclaredMethod("doSomething"));
+ testDefaultMethod(new DefaultImpl());
+ }
+
+ public static native void setSingleStepCallback();
+ public static native void setSingleStepUntil(Method m);
+ public static native void enableSingleStep(Thread thr);
+}
diff --git a/test/2262-default-conflict-methods/Android.bp b/test/2262-default-conflict-methods/Android.bp
new file mode 100644
index 0000000..8fb2f39
--- /dev/null
+++ b/test/2262-default-conflict-methods/Android.bp
@@ -0,0 +1,50 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2262-default-conflict-methods`.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "art_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["art_license"],
+}
+
+// Library with src/ sources for the test.
+java_library {
+ name: "art-run-test-2262-default-conflict-methods-src",
+ defaults: ["art-run-test-defaults"],
+ srcs: ["src/**/*.java"],
+}
+
+// Test's Dex code.
+java_test {
+ name: "art-run-test-2262-default-conflict-methods",
+ defaults: ["art-run-test-defaults"],
+ test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+ srcs: ["src2/**/*.java"],
+ static_libs: [
+ "art-run-test-2262-default-conflict-methods-src"
+ ],
+ data: [
+ ":art-run-test-2262-default-conflict-methods-expected-stdout",
+ ":art-run-test-2262-default-conflict-methods-expected-stderr",
+ ],
+}
+
+// Test's expected standard output.
+genrule {
+ name: "art-run-test-2262-default-conflict-methods-expected-stdout",
+ out: ["art-run-test-2262-default-conflict-methods-expected-stdout.txt"],
+ srcs: ["expected-stdout.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+ name: "art-run-test-2262-default-conflict-methods-expected-stderr",
+ out: ["art-run-test-2262-default-conflict-methods-expected-stderr.txt"],
+ srcs: ["expected-stderr.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/2262-default-conflict-methods/expected-stderr.txt b/test/2262-default-conflict-methods/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2262-default-conflict-methods/expected-stderr.txt
diff --git a/test/2262-default-conflict-methods/expected-stdout.txt b/test/2262-default-conflict-methods/expected-stdout.txt
new file mode 100644
index 0000000..818f313
--- /dev/null
+++ b/test/2262-default-conflict-methods/expected-stdout.txt
@@ -0,0 +1,10 @@
+JNI_OnLoad called
+Create Main instance
+Calling functions on concrete Main
+Calling non-conflicting function on Main
+Test from Interface
+Unexpected normal exit from GetMethodId
+Test from Interface
+Calling conflicting function on Main
+Expected ICCE on main
+Expected ICCE on main
diff --git a/test/2262-default-conflict-methods/info.txt b/test/2262-default-conflict-methods/info.txt
new file mode 100644
index 0000000..6e1a6a1
--- /dev/null
+++ b/test/2262-default-conflict-methods/info.txt
@@ -0,0 +1 @@
+Tests handling of method ids for default method conflicts.
diff --git a/test/2262-default-conflict-methods/src/Iface.java b/test/2262-default-conflict-methods/src/Iface.java
new file mode 100644
index 0000000..7ced383
--- /dev/null
+++ b/test/2262-default-conflict-methods/src/Iface.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 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 Iface {
+ public default void test() {
+ System.out.println("Test from Interface");
+ }
+
+ public default void test_throws() {
+ System.out.println("Test throws from Interface");
+ }
+}
diff --git a/test/2262-default-conflict-methods/src/Iface2.java b/test/2262-default-conflict-methods/src/Iface2.java
new file mode 100644
index 0000000..a7e9216
--- /dev/null
+++ b/test/2262-default-conflict-methods/src/Iface2.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2015 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 Iface2 extends Iface {}
diff --git a/test/2262-default-conflict-methods/src/Main.java b/test/2262-default-conflict-methods/src/Main.java
new file mode 100644
index 0000000..f916a48
--- /dev/null
+++ b/test/2262-default-conflict-methods/src/Main.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 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 Main implements Iface, Iface2 {
+ public static void main(String[] args) {
+ System.loadLibrary(args[0]);
+ System.out.println("Create Main instance");
+ Main m = new Main();
+ System.out.println("Calling functions on concrete Main");
+ callMain(m);
+ }
+
+ public static void callMain(Main m) {
+ System.out.println("Calling non-conflicting function on Main");
+ m.test();
+ long main_id = GetMethodId(false, Main.class, "test", "()V");
+ long iface_id = GetMethodId(false, Iface.class, "test", "()V");
+ try {
+ long iface2_id = GetMethodId(false, Main.class, "test", "()V");
+ System.out.println("Unexpected normal exit from GetMethodId");
+ } catch (NoSuchMethodError e) {
+ System.out.println("Expected NoSuchMethodError thrown on Iface2");
+ }
+ if (main_id != iface_id) {
+ throw new Error("Default methods have different method ids");
+ }
+ CallNonvirtual(m, Main.class, main_id);
+
+ System.out.println("Calling conflicting function on Main");
+ try {
+ m.test_throws();
+ } catch (IncompatibleClassChangeError e) {
+ System.out.println("Expected ICCE on main");
+ }
+
+ long main_throws_id = GetMethodId(false, Main.class, "test_throws", "()V");
+ long iface_throws_id = GetMethodId(false, Iface.class, "test_throws", "()V");
+ long iface2_throws_id = GetMethodId(false, Iface2.class, "test_throws", "()V");
+ if (main_throws_id == iface_throws_id || main_throws_id == iface2_throws_id) {
+ System.out.println(
+ "Unexpected: method id of default conflicting matches one of the interface methods");
+ }
+
+ try {
+ CallNonvirtual(m, Main.class, main_throws_id);
+ } catch (IncompatibleClassChangeError e) {
+ System.out.println("Expected ICCE on main");
+ }
+ return;
+ }
+
+ private static native long GetMethodId(boolean is_static, Class k, String name, String sig);
+ private static native long CallNonvirtual(Object obj, Class k, long methodid);
+}
diff --git a/test/2262-default-conflict-methods/src2/Iface2.java b/test/2262-default-conflict-methods/src2/Iface2.java
new file mode 100644
index 0000000..1c2f22f
--- /dev/null
+++ b/test/2262-default-conflict-methods/src2/Iface2.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2015 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 Iface2 {
+ public default void test_throws() {
+ System.out.println("TestThrows from Iface2");
+ }
+}
diff --git a/test/2262-miranda-methods/Android.bp b/test/2262-miranda-methods/Android.bp
new file mode 100644
index 0000000..0c00467
--- /dev/null
+++ b/test/2262-miranda-methods/Android.bp
@@ -0,0 +1,50 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2262-miranda-methods`.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "art_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["art_license"],
+}
+
+// Library with src/ sources for the test.
+java_library {
+ name: "art-run-test-2262-miranda-methods-src",
+ defaults: ["art-run-test-defaults"],
+ srcs: ["src/**/*.java"],
+}
+
+// Test's Dex code.
+java_test {
+ name: "art-run-test-2262-miranda-methods",
+ defaults: ["art-run-test-defaults"],
+ test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+ srcs: ["src2/**/*.java"],
+ static_libs: [
+ "art-run-test-2262-miranda-methods-src"
+ ],
+ data: [
+ ":art-run-test-2262-miranda-methods-expected-stdout",
+ ":art-run-test-2262-miranda-methods-expected-stderr",
+ ],
+}
+
+// Test's expected standard output.
+genrule {
+ name: "art-run-test-2262-miranda-methods-expected-stdout",
+ out: ["art-run-test-2262-miranda-methods-expected-stdout.txt"],
+ srcs: ["expected-stdout.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+ name: "art-run-test-2262-miranda-methods-expected-stderr",
+ out: ["art-run-test-2262-miranda-methods-expected-stderr.txt"],
+ srcs: ["expected-stderr.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/2262-miranda-methods/expected-stderr.txt b/test/2262-miranda-methods/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2262-miranda-methods/expected-stderr.txt
diff --git a/test/2262-miranda-methods/expected-stdout.txt b/test/2262-miranda-methods/expected-stdout.txt
new file mode 100644
index 0000000..4976738
--- /dev/null
+++ b/test/2262-miranda-methods/expected-stdout.txt
@@ -0,0 +1,12 @@
+JNI_OnLoad called
+Create Main instance
+Test method with concrete implementation
+Impl test correct - new
+Test method with concrete implementation via JNI call
+Abstract interface and Main have different method ids
+Impl test correct - new
+Test method with no concrete implementation
+Expected AME Thrown on Main
+Expected NoSuchMethodError on Main
+Abstract interface and Main have same method ids
+Expected AME Thrown on Main via JNI call
diff --git a/test/2262-miranda-methods/info.txt b/test/2262-miranda-methods/info.txt
new file mode 100644
index 0000000..6e1a6a1
--- /dev/null
+++ b/test/2262-miranda-methods/info.txt
@@ -0,0 +1 @@
+Tests handling of method ids for default method conflicts.
diff --git a/test/2262-miranda-methods/jni_invoke.cc b/test/2262-miranda-methods/jni_invoke.cc
new file mode 100644
index 0000000..da55f8b
--- /dev/null
+++ b/test/2262-miranda-methods/jni_invoke.cc
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include <jni.h>
+
+#include "jni/java_vm_ext.h"
+#include "runtime.h"
+
+namespace art {
+
+extern "C" JNIEXPORT void JNICALL
+Java_Main_CallNonvirtual(JNIEnv* env, jclass k ATTRIBUTE_UNUSED, jobject o, jclass c, jmethodID m) {
+ env->CallNonvirtualVoidMethod(o, c, m);
+}
+
+} // namespace art
diff --git a/test/2262-miranda-methods/src/Iface.java b/test/2262-miranda-methods/src/Iface.java
new file mode 100644
index 0000000..cbc2626
--- /dev/null
+++ b/test/2262-miranda-methods/src/Iface.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 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 Iface {
+ public abstract void test_throws();
+ public abstract void test_correct();
+}
diff --git a/test/2262-miranda-methods/src/Impl.java b/test/2262-miranda-methods/src/Impl.java
new file mode 100644
index 0000000..b0a241a
--- /dev/null
+++ b/test/2262-miranda-methods/src/Impl.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 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 Impl implements Iface {
+ public void test_correct() {
+ System.out.println("Impl test correct");
+ }
+
+ public void test_throws() {
+ System.out.println("Impl test throws");
+ }
+}
diff --git a/test/2262-miranda-methods/src/Main.java b/test/2262-miranda-methods/src/Main.java
new file mode 100644
index 0000000..9a890e6
--- /dev/null
+++ b/test/2262-miranda-methods/src/Main.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 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 Main extends Impl implements Iface {
+ public static void main(String[] args) {
+ System.loadLibrary(args[0]);
+ System.out.println("Create Main instance");
+ Main m = new Main();
+ callMain(m);
+ }
+
+ public static void callMain(Main m) {
+ System.out.println("Test method with concrete implementation");
+ m.test_correct();
+ System.out.println("Test method with concrete implementation via JNI call");
+ long iface_id = GetMethodId(false, Iface.class, "test_correct", "()V");
+ long main_id = GetMethodId(false, Main.class, "test_correct", "()V");
+ long impl_id = GetMethodId(false, Impl.class, "test_correct", "()V");
+ if (iface_id != main_id) {
+ System.out.println("Abstract interface and Main have different method ids");
+ } else {
+ System.out.println("Unexpected: Abstract interface and Main have same method ids");
+ }
+ CallNonvirtual(m, Main.class, main_id);
+
+ System.out.println("Test method with no concrete implementation");
+ try {
+ m.test_throws();
+ } catch (AbstractMethodError e) {
+ System.out.println("Expected AME Thrown on Main");
+ }
+ long iface_throws_id = GetMethodId(false, Iface.class, "test_throws", "()V");
+ long main_throws_id = GetMethodId(false, Main.class, "test_throws", "()V");
+ try {
+ long id = GetMethodId(false, Impl.class, "test_throws", "()V");
+ } catch (NoSuchMethodError e) {
+ System.out.println("Expected NoSuchMethodError on Main");
+ }
+ if (iface_throws_id == main_throws_id) {
+ System.out.println("Abstract interface and Main have same method ids");
+ } else {
+ System.out.println("Unexpected: Abstract interface and Main have different method ids");
+ }
+
+ try {
+ CallNonvirtual(m, Main.class, main_throws_id);
+ } catch (AbstractMethodError e) {
+ System.out.println("Expected AME Thrown on Main via JNI call");
+ }
+ return;
+ }
+
+ private static native long GetMethodId(boolean is_static, Class k, String name, String sig);
+ private static native long CallNonvirtual(Object obj, Class k, long methodid);
+}
diff --git a/test/2262-miranda-methods/src2/Impl.java b/test/2262-miranda-methods/src2/Impl.java
new file mode 100644
index 0000000..d7e8261
--- /dev/null
+++ b/test/2262-miranda-methods/src2/Impl.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2015 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 Impl {
+ public void test_correct() {
+ System.out.println("Impl test correct - new");
+ }
+}
diff --git a/test/Android.bp b/test/Android.bp
index 300f09f..6bc5990 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -752,6 +752,7 @@
"2005-pause-all-redefine-multithreaded/pause-all.cc",
"2009-structural-local-ref/local-ref.cc",
"2035-structural-native-method/structural-native.cc",
+ "2243-single-step-default/single_step_helper.cc"
],
// Use NDK-compatible headers for ctstiagent.
header_libs: [
@@ -989,6 +990,7 @@
"2037-thread-name-inherit/thread_name_inherit.cc",
"2040-huge-native-alloc/huge_native_buf.cc",
"2235-JdkUnsafeTest/unsafe_test.cc",
+ "2262-miranda-methods/jni_invoke.cc",
"common/runtime_state.cc",
"common/stack_inspect.cc",
],
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 69f9c27..f6c0e25 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1564,5 +1564,10 @@
"tests": ["845-data-image", "846-multidex-data-image"],
"variant": "debuggable",
"description": ["Runtime app images are not supported with debuggable."]
+ },
+ {
+ "tests": ["2262-miranda-methods"],
+ "variant": "jvm",
+ "description": ["jvm doesn't seem to support calling miranda methods via CallNonVirtual."]
}
]
diff --git a/test/utils/regen-test-files b/test/utils/regen-test-files
index cdc4e72..c7c0b1e 100755
--- a/test/utils/regen-test-files
+++ b/test/utils/regen-test-files
@@ -211,8 +211,11 @@
"844-exception2",
# 966-default-conflict: Dependency on `libarttest`.
"966-default-conflict",
- # 993-breakpoints-non-debuggable: This test needs native code.
+ # These tests need native code.
"993-breakpoints-non-debuggable",
+ "2243-single-step-default",
+ "2262-miranda-methods",
+ "2262-default-conflict-methods"
])
known_failing_on_hwasan_tests = frozenset([