JDK-8216324 : GetClassMethods is confused by the presence of default methods in super interfaces
  • Type: Bug
  • Component: hotspot
  • Sub-Component: jvmti
  • Affected Version: 12,13,14
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2019-01-08
  • Updated: 2021-02-05
  • Resolved: 2020-07-27
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 11 JDK 13 JDK 16
11.0.10-oracleFixed 13.0.7Fixed 16 b08Fixed
Related Reports
Relates :  
Description
As reported here:

http://mail.openjdk.java.net/pipermail/core-libs-dev/2019-January/057749.html

when a default method is present in a super interface, the information GetClassMethods returns for the sub-interface or implementing class, incorrectly includes an inherited method (not the default method itself!).

This can be seen with a simple modification to an existing test:

TEST: vmTestbase/nsk/jvmti/GetClassMethods/getclmthd007/TestDescription.java

diff -r 3da307766fb1 test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetClassMethods/getclmthd007.java
--- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetClassMethods/getclmthd007.java
+++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetClassMethods/getclmthd007.java
@@ -109,7 +110,11 @@
     }
 }

-interface OuterInterface1 {
+interface DefaultInterface {
+    default void default_method() { } // should never be seen
+}
+
+interface OuterInterface1 extends DefaultInterface {
     int meth_i1();
 }


STDOUT:
(7) wrong number of methods: 2, expected: 1
>>> OuterInterface2:
>>>   [0]: meth_i1()I   <= INHERITED from OuterInterface1
>>>   [1]: meth_i2()I
(8) wrong number of methods: 3, expected: 2
>>> OuterClass4:
>>>   [0]: <init>()V
>>>   [1]: meth_i1()I   <= INHERITED from OuterInterface1
>>>   [2]: meth_i2()I



Comments
Fix request (13u) Requesting backport to 13u for parity with 11u. The patch applies cleanly. Tested with tier1 and jvmti tests; new test fails without the patch, passes with it.
05-02-2021

Fix request (11u) -- will label after testing completed. I would like to downport this for parity with 11.0.10-oracle. I had to resolve C++ test files that must be C in 11: http://mail.openjdk.java.net/pipermail/jdk-updates-dev/2020-October/003922.html
07-10-2020

URL: https://hg.openjdk.java.net/jdk/jdk/rev/c379dc750a02 User: dtitov Date: 2020-07-27 18:38:24 +0000
27-07-2020

Considering to fix this in 16.
08-05-2020

The JVM intentionally does not create overpass methods if there is no default methods because the overpass methods are not needed. Instead, I believe it uses miranda methods to throw AbstractMethodError exceptions when needed. Reassigning this bug to JVMTI.
07-10-2019

ILW = MHM = P3
18-06-2019

To make it more clear about the runtime issue we think we observe ... The interface "Parent" is: interface Parent { default void def() {}; void method0(); void method1(); } The interface "Child" is: interface Child extends Parent { void method2(); } Two (synthetic) overpass methods are added by the ClassFileParser to the Child interface: method0() and method1() However, if the default method def() is removed from the Parent interface then two overpass methods are not added anymore. This behavior seems to be a bug. The two overpass methods have to be added (or not) independently of the method def() presence. For details on how to reproduce it, please see the comment from Alex above.
13-06-2019

Attached fix for JVMTI (GetClassMethods_jvmti_fix.patch) But the fix will mask bug in runtime, so I'm assigning the issue to runtime team for evaluation. Please assign back to JVMTI after evaluation if you think this is JVMTI only issue. Description of the supposed bug in runtime: - run InterfaceDefMethods from attached GetClassMethods_jvmti_fix.patch - it reports: Reflection getDeclaredMethods returned: [public abstract java.lang.String InterfaceDefMethods$Child.method2()] JVMTI GetClassMethods returned: [public default java.lang.String InterfaceDefMethods$Child.method0(), public default java.lang.String InterfaceDefMethods$Child.method1(), public abstract java.lang.String InterfaceDefMethods$Child.method2()] I.e. if Parent interface has default method, other method of Parent interface are reported by jvmti::GetClassMethods(Child) as syntetic bypass methods. This methods returned by GetClassMethods are from InstanceKlass::_methods list
13-06-2019

Anyway ClassFileParser can add synthetic bypass methods and GetClassMethods should filter them out
12-06-2019

I think, the suggested fix above is wrong. At least, there is more to look at here. The issue is the following strange behavior: - if the default method "Parent.def is present then the overpass methods method0 and method1 are added to the "Child" interface at parse/load time - these overpass methods are not generated in the Child interface if the the default method Parent.def() is absent. It looks very strange because the Parent.def() method has no relation to the methods Parent.method0() and Parent.method1(). It feels like the overpass methods method0() and method1() are added to the Child interface by a mistake. Then, it is a Runtime issue. Otherwise, I am probably missing something here.
12-06-2019

There is one suggestion to improve the test in comment above. It enables the capability can_maintain_original_method_order. We need to run the test twice: once with the capability enabled and once with the capability disabled. In fact, I've tested the fix in both modes and it has been passed.
11-06-2019

The overpassed methods have to be filtered out of the ik->methods() array. The m->is_overpassed() can be used to do so. This is a suggested fix with the jtreg version of the original test: diff -r f74f0d3033a9 src/hotspot/share/prims/jvmtiEnv.cpp --- a/src/hotspot/share/prims/jvmtiEnv.cpp Wed Jun 05 22:19:09 2019 -0700 +++ b/src/hotspot/share/prims/jvmtiEnv.cpp Mon Jun 10 22:56:06 2019 -0700 @@ -2486,6 +2486,7 @@ int result_length = ik->methods()->length(); jmethodID* result_list = (jmethodID*)jvmtiMalloc(result_length * sizeof(jmethodID)); int index; + int overpass_cnt = 0; bool jmethodids_found = true; if (JvmtiExport::can_maintain_original_method_order()) { @@ -2494,6 +2495,11 @@ for (index = 0; index < result_length; index++) { Method* m = ik->methods()->at(index); int original_index = ik->method_ordering()->at(index); + if (m->is_overpass()) { + overpass_cnt++; + result_list[original_index] = NULL; + continue; + } assert(original_index >= 0 && original_index < result_length, "invalid original method index"); jmethodID id; if (jmethodids_found) { @@ -2515,6 +2521,11 @@ // otherwise just copy in any order for (index = 0; index < result_length; index++) { Method* m = ik->methods()->at(index); + if (m->is_overpass()) { + overpass_cnt++; + result_list[index] = NULL; + continue; + } jmethodID id; if (jmethodids_found) { id = m->find_jmethod_id_or_null(); @@ -2532,9 +2543,21 @@ result_list[index] = id; } } + + jmethodID* methods = (jmethodID*)jvmtiMalloc((result_length - overpass_cnt) * sizeof(jmethodID)); + + for (int sidx = 0, didx = 0; sidx < result_length; sidx++) { + jmethodID id = result_list[sidx]; + if (id == NULL) { + continue; + } + methods[didx++] = id; + } + deallocate((unsigned char *)result_list); + // Fill in return value. - *method_count_ptr = result_length; - *methods_ptr = result_list; + *method_count_ptr = result_length - overpass_cnt; + *methods_ptr = methods; return JVMTI_ERROR_NONE; } /* end GetClassMethods */ diff -r f74f0d3033a9 test/hotspot/jtreg/serviceability/jvmti/GetClassMethods/GetClassMethodsTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/serviceability/jvmti/GetClassMethods/GetClassMethodsTest.java Mon Jun 10 22:56:06 2019 -0700 @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8216324 + * @summary GetClassMethods is confused by the presence of default methods in super interfaces + * @library /test/lib + * @compile GetClassMethodsTest.java + * @run main/othervm/native -agentlib:GetClassMethodsTest GetClassMethodsTest + */ + +import java.lang.reflect.Method; +import java.util.Arrays; + +public class GetClassMethodsTest { + + static { + try { + System.loadLibrary("GetClassMethodsTest"); + } catch (UnsatisfiedLinkError ex) { + System.err.println("Could not load GetClassMethodsTest library"); + System.err.println("java.library.path:" + + System.getProperty("java.library.path")); + throw ex; + } + } + + static private void log(String msg) { System.out.println(msg); } + + static private native Method[] getJVMTIDeclaredMethods(Class<?> klass); + + public interface Parent { + default String def() { return "Parent.def"; } + String method0(); + String method1(); + } + + public interface Child extends Parent { + String method2(); + } + + public static class Impl implements Child { + public String method0() { return "Impl.method0"; } + public String method1() { return "Impl.method1"; } + public String method2() { return "Impl.method2"; } + } + + public static void main(String[] args) { + new Impl(); // To get classes initialized + + Method[] reflectMethods = Child.class.getDeclaredMethods(); + Method[] jvmtiMethods = getJVMTIDeclaredMethods(Child.class); + + log("Reflection getDeclaredMethods returned: " + Arrays.toString(reflectMethods)); + log("JVMTI GetClassMethods returned: " + Arrays.toString(jvmtiMethods)); + + if (reflectMethods.length != jvmtiMethods.length) { + throw new RuntimeException("GetClassMethodsTest failed: Unexpected method count from JVMTI GetClassMethods!"); + } + if (!reflectMethods[0].equals(jvmtiMethods[0])) { + throw new RuntimeException("GetClassMethodsTest failed: Unexpected method from JVMTI GetClassMethods!"); + } + log("\nGetClassMethodsTest passed: Got expected output from JVMTI GetClassMethods!"); + } +} diff -r f74f0d3033a9 test/hotspot/jtreg/serviceability/jvmti/GetClassMethods/libGetClassMethodsTest.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/serviceability/jvmti/GetClassMethods/libGetClassMethodsTest.c Mon Jun 10 22:56:06 2019 -0700 @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include <stdio.h> +#include <string.h> +#include "jvmti.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static jvmtiEnv *jvmti = NULL; + +JNIEXPORT +jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) { + return JNI_VERSION_9; +} + +JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { + jvmtiCapabilities caps = { 0 }; + jvmtiError err; + + (*vm)->GetEnv(vm, (void **)&jvmti, JVMTI_VERSION_11); + + caps.can_maintain_original_method_order = 1; + + err = (*jvmti)->AddCapabilities(jvmti, &caps); + if (err != JVMTI_ERROR_NONE) { + printf("Agent_OnLoad: AddCapabilities failed with error: %d\n", err); + return JNI_ERR; + } + return JNI_OK; +} + +JNIEXPORT jobjectArray JNICALL Java_GetClassMethodsTest_getJVMTIDeclaredMethods(JNIEnv *env, jclass static_klass, jclass klass) { + jint method_count; + jmethodID* methods; + (*jvmti)->GetClassMethods(jvmti, klass, &method_count, &methods); + + jclass method_cls = (*env)->FindClass(env, "java/lang/reflect/Method"); + jobjectArray array = (*env)->NewObjectArray(env, method_count, method_cls, NULL); + + for (int i = 0; i < method_count; i++) { + char* mname = NULL; + jint modifiers = (*jvmti)->GetMethodModifiers(jvmti, methods[i], &modifiers); + + jobject m = (*env)->ToReflectedMethod(env, klass, methods[i], (modifiers & 8) == 8); + (*env)->SetObjectArrayElement(env, array, i, m); + + (*env)->DeleteLocalRef(env, m); + } + fflush(0); + (*jvmti)->Deallocate(jvmti, (unsigned char *)methods); + + return array; +} +#ifdef __cplusplus +} +#endif
11-06-2019

Also note in the original failure report that the inherited method has the wrong modifiers: public default java.lang.String app1.Test$Child.method() for a declaration: public interface Parent { default String def() { return "Parent"; } String method(); }
08-01-2019

Full patch for modified test, including additional debugging output on failure: diff -r 3da307766fb1 test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetClassMethods/getclmthd007.java --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetClassMethods/getclmthd007.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetClassMethods/getclmthd007.java @@ -81,6 +81,7 @@ } static interface InnerInterface { + default void meth_def1() {} void meth_n1(); } @@ -109,7 +110,11 @@ } } -interface OuterInterface1 { +interface DefaultInterface { + default void default_method() { } // should never be seen +} + +interface OuterInterface1 extends DefaultInterface { int meth_i1(); } diff -r 3da307766fb1 test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetClassMethods/getclmthd007/getclmthd007.cpp --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetClassMethods/getclmthd007/getclmthd007.cpp +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetClassMethods/getclmthd007/getclmthd007.cpp @@ -54,7 +54,8 @@ }; static meth_info m1[] = { - { "meth_n1", "()V" } + { "meth_n1", "()V" }, + { "meth_def1", "()V" } }; static meth_info m2[] = { @@ -98,7 +99,7 @@ static class_info classes[] = { { "InnerClass1", 2, m0 }, - { "InnerInterface", 1, m1 }, + { "InnerInterface", 2, m1 }, { "InnerClass2", 4, m2 }, { "OuterClass1", 1, m3 }, { "OuterClass2", 2, m4 }, @@ -144,7 +145,7 @@ jmethodID *methods; char *name, *sig, *generic; int j, k; - + int failed = JNI_FALSE; // enable debugging on failure if (jvmti == NULL) { printf("JVMTI client was not properly loaded!\n"); result = STATUS_FAILED; @@ -167,12 +168,14 @@ printf("(%d) wrong number of methods: %d, expected: %d\n", i, mcount, classes[i].mcount); result = STATUS_FAILED; + failed = JNI_TRUE; // show the methods found + printf(">>> %s:\n", classes[i].name); } for (k = 0; k < mcount; k++) { if (methods[k] == NULL) { printf("(%d:%d) methodID = null\n", i, k); result = STATUS_FAILED; - } else if (printdump == JNI_TRUE) { + } else if (printdump == JNI_TRUE || failed == JNI_TRUE) { err = jvmti->GetMethodName(methods[k], &name, &sig, &generic); if (err == JVMTI_ERROR_NONE) {
08-01-2019