# HG changeset patch # User coleenp # Date 1551186080 18000 # Node ID 09cba396916fda80a820a79921af529edc1a9e4a # Parent c431ab7f9e85389ac117a7ce298bd6cfe5a58b3a 8181171: Deleting method for RedefineClasses breaks ResolvedMethodName 8210457: JVM crash in ResolvedMethodTable::add_method(Handle) Summary: Add a function to call NSME in ResolvedMethodTable to replace deleted methods. Reviewed-by: sspitsyn, dholmes, dcubed diff -r c431ab7f9e85 -r 09cba396916f src/hotspot/share/classfile/javaClasses.cpp --- a/src/hotspot/share/classfile/javaClasses.cpp Wed Feb 13 12:01:09 2019 +0100 +++ b/src/hotspot/share/classfile/javaClasses.cpp Tue Feb 26 08:01:20 2019 -0500 @@ -3635,7 +3635,7 @@ // distinct loaders) to ensure the metadata is kept alive. // This mirror may be different than the one in clazz field. new_resolved_method->obj_field_put(_vmholder_offset, m->method_holder()->java_mirror()); - resolved_method = ResolvedMethodTable::add_method(Handle(THREAD, new_resolved_method)); + resolved_method = ResolvedMethodTable::add_method(m, Handle(THREAD, new_resolved_method)); } return resolved_method; } diff -r c431ab7f9e85 -r 09cba396916f src/hotspot/share/memory/universe.cpp --- a/src/hotspot/share/memory/universe.cpp Wed Feb 13 12:01:09 2019 +0100 +++ b/src/hotspot/share/memory/universe.cpp Tue Feb 26 08:01:20 2019 -0500 @@ -107,6 +107,7 @@ LatestMethodCache* Universe::_finalizer_register_cache = NULL; LatestMethodCache* Universe::_loader_addClass_cache = NULL; LatestMethodCache* Universe::_throw_illegal_access_error_cache = NULL; +LatestMethodCache* Universe::_throw_no_such_method_error_cache = NULL; LatestMethodCache* Universe::_do_stack_walk_cache = NULL; oop Universe::_out_of_memory_error_java_heap = NULL; oop Universe::_out_of_memory_error_metaspace = NULL; @@ -230,6 +231,7 @@ _finalizer_register_cache->metaspace_pointers_do(it); _loader_addClass_cache->metaspace_pointers_do(it); _throw_illegal_access_error_cache->metaspace_pointers_do(it); + _throw_no_such_method_error_cache->metaspace_pointers_do(it); _do_stack_walk_cache->metaspace_pointers_do(it); } @@ -271,6 +273,7 @@ _finalizer_register_cache->serialize(f); _loader_addClass_cache->serialize(f); _throw_illegal_access_error_cache->serialize(f); + _throw_no_such_method_error_cache->serialize(f); _do_stack_walk_cache->serialize(f); } @@ -689,6 +692,7 @@ Universe::_finalizer_register_cache = new LatestMethodCache(); Universe::_loader_addClass_cache = new LatestMethodCache(); Universe::_throw_illegal_access_error_cache = new LatestMethodCache(); + Universe::_throw_no_such_method_error_cache = new LatestMethodCache(); Universe::_do_stack_walk_cache = new LatestMethodCache(); #if INCLUDE_CDS @@ -935,6 +939,11 @@ "throwIllegalAccessError", vmSymbols::void_method_signature(), true, CHECK); + initialize_known_method(_throw_no_such_method_error_cache, + SystemDictionary::internal_Unsafe_klass(), + "throwNoSuchMethodError", + vmSymbols::void_method_signature(), true, CHECK); + // Set up method for registering loaded classes in class loader vector initialize_known_method(_loader_addClass_cache, SystemDictionary::ClassLoader_klass(), diff -r c431ab7f9e85 -r 09cba396916f src/hotspot/share/memory/universe.hpp --- a/src/hotspot/share/memory/universe.hpp Wed Feb 13 12:01:09 2019 +0100 +++ b/src/hotspot/share/memory/universe.hpp Tue Feb 26 08:01:20 2019 -0500 @@ -138,6 +138,7 @@ static LatestMethodCache* _finalizer_register_cache; // static method for registering finalizable objects static LatestMethodCache* _loader_addClass_cache; // method for registering loaded classes in class loader vector static LatestMethodCache* _throw_illegal_access_error_cache; // Unsafe.throwIllegalAccessError() method + static LatestMethodCache* _throw_no_such_method_error_cache; // Unsafe.throwNoSuchMethodError() method static LatestMethodCache* _do_stack_walk_cache; // method for stack walker callback // preallocated error objects (no backtrace) @@ -322,6 +323,7 @@ static Method* loader_addClass_method() { return _loader_addClass_cache->get_method(); } static Method* throw_illegal_access_error() { return _throw_illegal_access_error_cache->get_method(); } + static Method* throw_no_such_method_error() { return _throw_no_such_method_error_cache->get_method(); } static Method* do_stack_walk_method() { return _do_stack_walk_cache->get_method(); } diff -r c431ab7f9e85 -r 09cba396916f src/hotspot/share/oops/method.cpp --- a/src/hotspot/share/oops/method.cpp Wed Feb 13 12:01:09 2019 +0100 +++ b/src/hotspot/share/oops/method.cpp Tue Feb 26 08:01:20 2019 -0500 @@ -2120,7 +2120,8 @@ // Can't assert the method_holder is the same because the new method has the // scratch method holder. assert(resolve_jmethod_id(jmid)->method_holder()->class_loader() - == new_method->method_holder()->class_loader(), + == new_method->method_holder()->class_loader() || + new_method->method_holder()->class_loader() == NULL, // allow Unsafe substitution "changing to a different class loader"); // Just change the method in place, jmethodID pointer doesn't change. *((Method**)jmid) = new_method; diff -r c431ab7f9e85 -r 09cba396916f src/hotspot/share/prims/jvmtiRedefineClasses.cpp --- a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp Wed Feb 13 12:01:09 2019 +0100 +++ b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp Tue Feb 26 08:01:20 2019 -0500 @@ -3525,6 +3525,15 @@ "should be replaced"); } } + // Update deleted jmethodID + for (int j = 0; j < _deleted_methods_length; ++j) { + Method* old_method = _deleted_methods[j]; + jmethodID jmid = old_method->find_jmethod_id_or_null(); + if (jmid != NULL) { + // Change the jmethodID to point to NSME. + Method::change_method_associated_with_jmethod_id(jmid, Universe::throw_no_such_method_error()); + } + } } int VM_RedefineClasses::check_methods_and_mark_as_obsolete() { diff -r c431ab7f9e85 -r 09cba396916f src/hotspot/share/prims/resolvedMethodTable.cpp --- a/src/hotspot/share/prims/resolvedMethodTable.cpp Wed Feb 13 12:01:09 2019 +0100 +++ b/src/hotspot/share/prims/resolvedMethodTable.cpp Tue Feb 26 08:01:20 2019 -0500 @@ -120,18 +120,21 @@ return entry; } -oop ResolvedMethodTable::add_method(Handle resolved_method_name) { +oop ResolvedMethodTable::add_method(const methodHandle& m, Handle resolved_method_name) { MutexLocker ml(ResolvedMethodTable_lock); DEBUG_ONLY(NoSafepointVerifier nsv); + Method* method = m(); // Check if method has been redefined while taking out ResolvedMethodTable_lock, if so - // use new method. - Method* method = (Method*)java_lang_invoke_ResolvedMethodName::vmtarget(resolved_method_name()); - assert(method->is_method(), "must be method"); + // use new method. The old method won't be deallocated because it's passed in as a Handle. if (method->is_old()) { // Replace method with redefined version InstanceKlass* holder = method->method_holder(); method = holder->method_with_idnum(method->method_idnum()); + if (method == NULL) { + // Replace deleted method with NSME. + method = Universe::throw_no_such_method_error(); + } java_lang_invoke_ResolvedMethodName::set_vmtarget(resolved_method_name(), method); } // Set flag in class to indicate this InstanceKlass has entries in the table @@ -226,13 +229,9 @@ if (old_method->is_old()) { - if (old_method->is_deleted()) { - // leave deleted method in ResolvedMethod for now (this is a bug that we don't mark - // these on_stack) - continue; - } - - Method* new_method = old_method->get_new_method(); + Method* new_method = (old_method->is_deleted()) ? + Universe::throw_no_such_method_error() : + old_method->get_new_method(); java_lang_invoke_ResolvedMethodName::set_vmtarget(mem_name, new_method); ResourceMark rm; diff -r c431ab7f9e85 -r 09cba396916f src/hotspot/share/prims/resolvedMethodTable.hpp --- a/src/hotspot/share/prims/resolvedMethodTable.hpp Wed Feb 13 12:01:09 2019 +0100 +++ b/src/hotspot/share/prims/resolvedMethodTable.hpp Tue Feb 26 08:01:20 2019 -0500 @@ -89,7 +89,7 @@ // Called from java_lang_invoke_ResolvedMethodName static oop find_method(Method* method); - static oop add_method(Handle rmethod_name); + static oop add_method(const methodHandle& method, Handle rmethod_name); static bool has_work() { return _dead_entries; } static void trigger_cleanup(); diff -r c431ab7f9e85 -r 09cba396916f src/java.base/share/classes/jdk/internal/misc/Unsafe.java --- a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java Wed Feb 13 12:01:09 2019 +0100 +++ b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java Tue Feb 26 08:01:20 2019 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -3114,7 +3114,7 @@ * @param offset field/element offset * @param mask the mask value * @return the previous value - * @since 1.9 + * @since 9 */ @ForceInline public final int getAndBitwiseAndInt(Object o, long offset, int mask) { @@ -3343,6 +3343,14 @@ } /** + * Throws NoSuchMethodError; for use by the VM for redefinition support. + * @since 13 + */ + private static void throwNoSuchMethodError() { + throw new NoSuchMethodError(); + } + + /** * @return Returns true if the native byte ordering of this * platform is big-endian, false if it is little-endian. */ diff -r c431ab7f9e85 -r 09cba396916f test/hotspot/jtreg/runtime/RedefineTests/RedefineDeleteJmethod.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/RedefineTests/RedefineDeleteJmethod.java Tue Feb 26 08:01:20 2019 -0500 @@ -0,0 +1,94 @@ +/* + * 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 8181171 + * @summary Test deleting static method pointing to by a jmethod + * @library /test/lib + * @modules java.base/jdk.internal.misc + * @modules java.compiler + * java.instrument + * jdk.jartool/sun.tools.jar + * @run main RedefineClassHelper + * @run main/native/othervm -javaagent:redefineagent.jar -Xlog:redefine+class*=trace RedefineDeleteJmethod + */ + +class B { + private static int deleteMe() { System.out.println("deleteMe called"); return 5; } + public static int callDeleteMe() { return deleteMe(); } +} + +public class RedefineDeleteJmethod { + + public static String newB = + "class B {" + + "public static int callDeleteMe() { return 6; }" + + "}"; + + public static String newerB = + "class B {" + + "private static int deleteMe() { System.out.println(\"deleteMe (2) called\"); return 7; }" + + "public static int callDeleteMe() { return deleteMe(); }" + + "}"; + + + static { + System.loadLibrary("RedefineDeleteJmethod"); + } + + static native int jniCallDeleteMe(); + + static void test(int expected, boolean nsme_expected) throws Exception { + // Call through static method + int res = B.callDeleteMe(); + System.out.println("Result = " + res); + if (res != expected) { + throw new Error("returned " + res + " expected " + expected); + } + + // Call through jmethodID, saved from first call. + try { + res = jniCallDeleteMe(); + if (nsme_expected) { + throw new RuntimeException("Failed, NoSuchMethodError expected"); + } + if (res != expected) { + throw new Error("returned " + res + " expected " + expected); + } + } catch (NoSuchMethodError ex) { + if (!nsme_expected) { + throw new RuntimeException("Failed, NoSuchMethodError not expected"); + } + System.out.println("Passed, NoSuchMethodError expected"); + } + } + + public static void main(String[] args) throws Exception { + test(5, false); + RedefineClassHelper.redefineClass(B.class, newB); + test(6, true); + RedefineClassHelper.redefineClass(B.class, newerB); + test(7, true); + } +} diff -r c431ab7f9e85 -r 09cba396916f test/hotspot/jtreg/runtime/RedefineTests/libRedefineDeleteJmethod.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/RedefineTests/libRedefineDeleteJmethod.c Tue Feb 26 08:01:20 2019 -0500 @@ -0,0 +1,47 @@ +/* + * 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 + +jmethodID mid; +jclass cls; +static int count = 0; + +JNIEXPORT jint JNICALL +Java_RedefineDeleteJmethod_jniCallDeleteMe(JNIEnv* env, jobject obj) { + + if (count == 0) { + count++; + cls = (*env)->FindClass(env, "B"); + if (NULL == cls) { + (*env)->FatalError(env, "could not find class"); + } + + mid = (*env)->GetStaticMethodID(env, cls, "deleteMe", "()I"); + if (NULL == mid) { + (*env)->FatalError(env, "could not find method"); + } + } + + return (*env)->CallStaticIntMethod(env, cls, mid); +} diff -r c431ab7f9e85 -r 09cba396916f test/jdk/java/lang/instrument/NamedBuffer.java --- a/test/jdk/java/lang/instrument/NamedBuffer.java Wed Feb 13 12:01:09 2019 +0100 +++ b/test/jdk/java/lang/instrument/NamedBuffer.java Tue Feb 26 08:01:20 2019 -0500 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -64,7 +64,7 @@ public static byte[] loadBufferFromStream(InputStream stream) throws IOException - { + { // hack for now, just assume the stream will fit in our reasonable size buffer. // if not, panic int bufferLimit = 200 * 1024; @@ -83,5 +83,60 @@ 0, actualSize); return resultBuffer; + } + + static final String DEST = System.getProperty("test.classes"); + static final boolean VERBOSE = false; + + static boolean checkMatch(byte[] buf, byte[] name, int begIdx) { + if (buf.length < name.length + begIdx) { + return false; } + for (int i = 0; i < name.length; i++) { + if (buf[i + begIdx] != name[i]) { + return false; + } + } + return true; + } + + // This function reads a class file from disk and replaces the first character of + // the name with the one passed as "replace". Then goes through the bytecodes to + // replace all instances of the name of the class with the new name. The + // redefinition tests use this to redefine Host$ classes with precompiled class files + // Xost.java, Yost.java and Zost.java. + static byte[] + bytesForHostClass(char replace, String className) throws Throwable { + String tail = className.substring(1); + String origClassName = "" + replace + tail; + File clsfile = new File(DEST + "/" + origClassName + ".class"); + + if (VERBOSE) { + System.out.println(" Reading bytes from " + clsfile); + } + byte[] buf = null; + try (FileInputStream str = new FileInputStream(clsfile)) { + buf = loadBufferFromStream(str); + } + + boolean found = false; + int dollarSignIdx = className.indexOf('$'); + int ptrnLen = (dollarSignIdx == -1) ? className.length() : dollarSignIdx; + byte[] ptrnBytes = origClassName.substring(0, ptrnLen).getBytes(); + byte firstByte = className.getBytes()[0]; + + for (int i = 0; i < buf.length - ptrnLen; i++) { + if (checkMatch(buf, ptrnBytes, i)) { + if (VERBOSE) { + System.out.println("Appear to have found " + origClassName + " starting at " + i); + } + buf[i] = firstByte; + found = true; + } + } + if (!found) { + throw new Error("Could not locate '" + ptrnBytes + "' name in byte array"); + } + return buf; + } } diff -r c431ab7f9e85 -r 09cba396916f test/jdk/java/lang/instrument/RedefineAddDeleteMethod/DeleteMethodHandle/MethodHandleDeletedMethod.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/java/lang/instrument/RedefineAddDeleteMethod/DeleteMethodHandle/MethodHandleDeletedMethod.java Tue Feb 26 08:01:20 2019 -0500 @@ -0,0 +1,97 @@ +/* + * 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 8181171 + * @summary Break ResolvedMethodTable with redefined nest class. + * @library /test/lib + * @modules java.base/jdk.internal.misc + * @modules java.compiler + * java.instrument + * jdk.jartool/sun.tools.jar + * @compile ../../NamedBuffer.java + * @compile redef/Xost.java + * @run main RedefineClassHelper + * @run main/othervm -javaagent:redefineagent.jar -Xlog:redefine+class+update*=debug,membername+table=debug MethodHandleDeletedMethod + */ + +import java.io.File; +import java.io.FileInputStream; +import java.lang.invoke.*; + +class Host { + static MethodHandle fooMH; + + static class A { + private static void foo() { System.out.println("OLD foo called"); } + } + static void bar() throws NoSuchMethodError { + A.foo(); + } + static void barMH() throws Throwable { + fooMH.invokeExact(); + } + + public static void reresolve() throws Throwable { + fooMH = MethodHandles.lookup().findStatic(A.class, "foo", MethodType.methodType(void.class)); + } + + static { + try { + fooMH = MethodHandles.lookup().findStatic(A.class, "foo", MethodType.methodType(void.class)); + } catch (ReflectiveOperationException ex) { + } + } +} + +public class MethodHandleDeletedMethod { + + static final String DEST = System.getProperty("test.classes"); + static final boolean VERBOSE = false; + + private static byte[] bytesForHostClass(char replace) throws Throwable { + return NamedBuffer.bytesForHostClass(replace, "Host$A"); + } + + public static void main(java.lang.String[] unused) throws Throwable { + Host h = new Host(); + h.bar(); + h.barMH(); + byte[] buf = bytesForHostClass('X'); + RedefineClassHelper.redefineClass(Host.A.class, buf); + try { + h.bar(); // call deleted Method directly + throw new RuntimeException("Failed, expected NSME"); + } catch (NoSuchMethodError nsme) { + System.out.println("Received expected NSME"); + } + try { + h.barMH(); // call through MethodHandle for deleted Method + throw new RuntimeException("Failed, expected NSME"); + } catch (NoSuchMethodError nsme) { + System.out.println("Received expected NSME"); + } + System.out.println("Passed."); + } +} diff -r c431ab7f9e85 -r 09cba396916f test/jdk/java/lang/instrument/RedefineAddDeleteMethod/DeleteMethodHandle/redef/Xost.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/java/lang/instrument/RedefineAddDeleteMethod/DeleteMethodHandle/redef/Xost.java Tue Feb 26 08:01:20 2019 -0500 @@ -0,0 +1,29 @@ +/* + * 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. + */ + +public class Xost { + // Remove static private methods, in A in redefinition. + static class A { } + // Removed public method to get this to compile, but we don't + // try to redefine Host. +}