8232613: Move Object.registerNatives into HotSpot
Reviewed-by: dholmes, adinn, coleenp, lfoltan, mchung
--- a/src/hotspot/share/classfile/javaClasses.cpp Wed Oct 23 18:43:18 2019 -0700
+++ b/src/hotspot/share/classfile/javaClasses.cpp Thu Oct 24 09:57:29 2019 +0200
@@ -87,6 +87,21 @@
ALL_INJECTED_FIELDS(DECLARE_INJECTED_FIELD)
};
+// Register native methods of Object
+void java_lang_Object::register_natives(TRAPS) {
+ InstanceKlass* obj = SystemDictionary::Object_klass();
+ Method::register_native(obj, vmSymbols::hashCode_name(),
+ vmSymbols::void_int_signature(), (address) &JVM_IHashCode, CHECK);
+ Method::register_native(obj, vmSymbols::wait_name(),
+ vmSymbols::long_void_signature(), (address) &JVM_MonitorWait, CHECK);
+ Method::register_native(obj, vmSymbols::notify_name(),
+ vmSymbols::void_method_signature(), (address) &JVM_MonitorNotify, CHECK);
+ Method::register_native(obj, vmSymbols::notifyAll_name(),
+ vmSymbols::void_method_signature(), (address) &JVM_MonitorNotifyAll, CHECK);
+ Method::register_native(obj, vmSymbols::clone_name(),
+ vmSymbols::void_object_signature(), (address) &JVM_Clone, THREAD);
+}
+
int JavaClasses::compute_injected_offset(InjectedFieldID id) {
return _injected_fields[id].compute_offset();
}
--- a/src/hotspot/share/classfile/javaClasses.hpp Wed Oct 23 18:43:18 2019 -0700
+++ b/src/hotspot/share/classfile/javaClasses.hpp Thu Oct 24 09:57:29 2019 +0200
@@ -88,6 +88,13 @@
BASIC_JAVA_CLASSES_DO_PART1(f) \
BASIC_JAVA_CLASSES_DO_PART2(f)
+// Interface to java.lang.Object objects
+
+class java_lang_Object : AllStatic {
+ public:
+ static void register_natives(TRAPS);
+};
+
// Interface to java.lang.String objects
class java_lang_String : AllStatic {
--- a/src/hotspot/share/classfile/systemDictionary.cpp Wed Oct 23 18:43:18 2019 -0700
+++ b/src/hotspot/share/classfile/systemDictionary.cpp Thu Oct 24 09:57:29 2019 +0200
@@ -1972,6 +1972,10 @@
resolve_wk_klasses_through(WK_KLASS_ENUM_NAME(Class_klass), scan, CHECK);
}
+ assert(WK_KLASS(Object_klass) != NULL, "well-known classes should now be initialized");
+
+ java_lang_Object::register_natives(CHECK);
+
// Calculate offsets for String and Class classes since they are loaded and
// can be used after this point.
java_lang_String::compute_offsets();
--- a/src/hotspot/share/classfile/systemDictionary.hpp Wed Oct 23 18:43:18 2019 -0700
+++ b/src/hotspot/share/classfile/systemDictionary.hpp Thu Oct 24 09:57:29 2019 +0200
@@ -219,7 +219,6 @@
\
/*end*/
-
class SystemDictionary : AllStatic {
friend class BootstrapInfo;
friend class VMStructs;
@@ -383,7 +382,6 @@
int limit = (int)end_id + 1;
resolve_wk_klasses_until((WKID) limit, start_id, THREAD);
}
-
public:
#define WK_KLASS_DECLARE(name, symbol) \
static InstanceKlass* name() { return check_klass(_well_known_klasses[WK_KLASS_ENUM_NAME(name)]); } \
@@ -628,21 +626,6 @@
// Basic find on classes in the midst of being loaded
static Symbol* find_placeholder(Symbol* name, ClassLoaderData* loader_data);
- // Add a placeholder for a class being loaded
- static void add_placeholder(int index,
- Symbol* class_name,
- ClassLoaderData* loader_data);
- static void remove_placeholder(int index,
- Symbol* class_name,
- ClassLoaderData* loader_data);
-
- // Performs cleanups after resolve_super_or_fail. This typically needs
- // to be called on failure.
- // Won't throw, but can block.
- static void resolution_cleanups(Symbol* class_name,
- ClassLoaderData* loader_data,
- TRAPS);
-
// Resolve well-known classes so they can be used like SystemDictionary::String_klass()
static void resolve_well_known_classes(TRAPS);
--- a/src/hotspot/share/oops/method.cpp Wed Oct 23 18:43:18 2019 -0700
+++ b/src/hotspot/share/oops/method.cpp Thu Oct 24 09:57:29 2019 +0200
@@ -25,6 +25,7 @@
#include "precompiled.hpp"
#include "classfile/classLoaderDataGraph.hpp"
#include "classfile/metadataOnStackMark.hpp"
+#include "classfile/symbolTable.hpp"
#include "classfile/systemDictionary.hpp"
#include "code/codeCache.hpp"
#include "code/debugInfoRec.hpp"
@@ -378,7 +379,83 @@
assert(valid_itable_index(), "");
}
+// The RegisterNatives call being attempted tried to register with a method that
+// is not native. Ask JVM TI what prefixes have been specified. Then check
+// to see if the native method is now wrapped with the prefixes. See the
+// SetNativeMethodPrefix(es) functions in the JVM TI Spec for details.
+static Method* find_prefixed_native(Klass* k, Symbol* name, Symbol* signature, TRAPS) {
+#if INCLUDE_JVMTI
+ ResourceMark rm(THREAD);
+ Method* method;
+ int name_len = name->utf8_length();
+ char* name_str = name->as_utf8();
+ int prefix_count;
+ char** prefixes = JvmtiExport::get_all_native_method_prefixes(&prefix_count);
+ for (int i = 0; i < prefix_count; i++) {
+ char* prefix = prefixes[i];
+ int prefix_len = (int)strlen(prefix);
+ // try adding this prefix to the method name and see if it matches another method name
+ int trial_len = name_len + prefix_len;
+ char* trial_name_str = NEW_RESOURCE_ARRAY(char, trial_len + 1);
+ strcpy(trial_name_str, prefix);
+ strcat(trial_name_str, name_str);
+ TempNewSymbol trial_name = SymbolTable::probe(trial_name_str, trial_len);
+ if (trial_name == NULL) {
+ continue; // no such symbol, so this prefix wasn't used, try the next prefix
+ }
+ method = k->lookup_method(trial_name, signature);
+ if (method == NULL) {
+ continue; // signature doesn't match, try the next prefix
+ }
+ if (method->is_native()) {
+ method->set_is_prefixed_native();
+ return method; // wahoo, we found a prefixed version of the method, return it
+ }
+ // found as non-native, so prefix is good, add it, probably just need more prefixes
+ name_len = trial_len;
+ name_str = trial_name_str;
+ }
+#endif // INCLUDE_JVMTI
+ return NULL; // not found
+}
+
+bool Method::register_native(Klass* k, Symbol* name, Symbol* signature, address entry, TRAPS) {
+ Method* method = k->lookup_method(name, signature);
+ if (method == NULL) {
+ ResourceMark rm(THREAD);
+ stringStream st;
+ st.print("Method '");
+ print_external_name(&st, k, name, signature);
+ st.print("' name or signature does not match");
+ THROW_MSG_(vmSymbols::java_lang_NoSuchMethodError(), st.as_string(), false);
+ }
+ if (!method->is_native()) {
+ // trying to register to a non-native method, see if a JVM TI agent has added prefix(es)
+ method = find_prefixed_native(k, name, signature, THREAD);
+ if (method == NULL) {
+ ResourceMark rm(THREAD);
+ stringStream st;
+ st.print("Method '");
+ print_external_name(&st, k, name, signature);
+ st.print("' is not declared as native");
+ THROW_MSG_(vmSymbols::java_lang_NoSuchMethodError(), st.as_string(), false);
+ }
+ }
+
+ if (entry != NULL) {
+ method->set_native_function(entry, native_bind_event_is_interesting);
+ } else {
+ method->clear_native_function();
+ }
+ if (PrintJNIResolving) {
+ ResourceMark rm(THREAD);
+ tty->print_cr("[Registering JNI native method %s.%s]",
+ method->method_holder()->external_name(),
+ method->name()->as_C_string());
+ }
+ return true;
+}
bool Method::was_executed_more_than(int n) {
// Invocation counter is reset when the Method* is compiled.
--- a/src/hotspot/share/oops/method.hpp Wed Oct 23 18:43:18 2019 -0700
+++ b/src/hotspot/share/oops/method.hpp Thu Oct 24 09:57:29 2019 +0200
@@ -346,6 +346,12 @@
// InterpreterRuntime::exception_handler_for_exception.
static int fast_exception_handler_bci_for(const methodHandle& mh, Klass* ex_klass, int throw_bci, TRAPS);
+ static bool register_native(Klass* k,
+ Symbol* name,
+ Symbol* signature,
+ address entry,
+ TRAPS);
+
// method data access
MethodData* method_data() const {
return _method_data;
--- a/src/hotspot/share/prims/jni.cpp Wed Oct 23 18:43:18 2019 -0700
+++ b/src/hotspot/share/prims/jni.cpp Thu Oct 24 09:57:29 2019 +0200
@@ -2909,89 +2909,6 @@
HOTSPOT_JNI_SETDOUBLEARRAYREGION_RETURN())
-//
-// Interception of natives
-//
-
-// The RegisterNatives call being attempted tried to register with a method that
-// is not native. Ask JVM TI what prefixes have been specified. Then check
-// to see if the native method is now wrapped with the prefixes. See the
-// SetNativeMethodPrefix(es) functions in the JVM TI Spec for details.
-static Method* find_prefixed_native(Klass* k, Symbol* name, Symbol* signature, TRAPS) {
-#if INCLUDE_JVMTI
- ResourceMark rm(THREAD);
- Method* method;
- int name_len = name->utf8_length();
- char* name_str = name->as_utf8();
- int prefix_count;
- char** prefixes = JvmtiExport::get_all_native_method_prefixes(&prefix_count);
- for (int i = 0; i < prefix_count; i++) {
- char* prefix = prefixes[i];
- int prefix_len = (int)strlen(prefix);
-
- // try adding this prefix to the method name and see if it matches another method name
- int trial_len = name_len + prefix_len;
- char* trial_name_str = NEW_RESOURCE_ARRAY(char, trial_len + 1);
- strcpy(trial_name_str, prefix);
- strcat(trial_name_str, name_str);
- TempNewSymbol trial_name = SymbolTable::probe(trial_name_str, trial_len);
- if (trial_name == NULL) {
- continue; // no such symbol, so this prefix wasn't used, try the next prefix
- }
- method = k->lookup_method(trial_name, signature);
- if (method == NULL) {
- continue; // signature doesn't match, try the next prefix
- }
- if (method->is_native()) {
- method->set_is_prefixed_native();
- return method; // wahoo, we found a prefixed version of the method, return it
- }
- // found as non-native, so prefix is good, add it, probably just need more prefixes
- name_len = trial_len;
- name_str = trial_name_str;
- }
-#endif // INCLUDE_JVMTI
- return NULL; // not found
-}
-
-static bool register_native(Klass* k, Symbol* name, Symbol* signature, address entry, TRAPS) {
- Method* method = k->lookup_method(name, signature);
- if (method == NULL) {
- ResourceMark rm;
- stringStream st;
- st.print("Method '");
- Method::print_external_name(&st, k, name, signature);
- st.print("' name or signature does not match");
- THROW_MSG_(vmSymbols::java_lang_NoSuchMethodError(), st.as_string(), false);
- }
- if (!method->is_native()) {
- // trying to register to a non-native method, see if a JVM TI agent has added prefix(es)
- method = find_prefixed_native(k, name, signature, THREAD);
- if (method == NULL) {
- ResourceMark rm;
- stringStream st;
- st.print("Method '");
- Method::print_external_name(&st, k, name, signature);
- st.print("' is not declared as native");
- THROW_MSG_(vmSymbols::java_lang_NoSuchMethodError(), st.as_string(), false);
- }
- }
-
- if (entry != NULL) {
- method->set_native_function(entry,
- Method::native_bind_event_is_interesting);
- } else {
- method->clear_native_function();
- }
- if (PrintJNIResolving) {
- ResourceMark rm(THREAD);
- tty->print_cr("[Registering JNI native method %s.%s]",
- method->method_holder()->external_name(),
- method->name()->as_C_string());
- }
- return true;
-}
-
DT_RETURN_MARK_DECL(RegisterNatives, jint
, HOTSPOT_JNI_REGISTERNATIVES_RETURN(_ret_ref));
@@ -3024,8 +2941,8 @@
THROW_MSG_(vmSymbols::java_lang_NoSuchMethodError(), st.as_string(), -1);
}
- bool res = register_native(k, name, signature,
- (address) methods[index].fnPtr, THREAD);
+ bool res = Method::register_native(k, name, signature,
+ (address) methods[index].fnPtr, THREAD);
if (!res) {
ret = -1;
break;
--- a/src/java.base/share/classes/java/lang/Object.java Wed Oct 23 18:43:18 2019 -0700
+++ b/src/java.base/share/classes/java/lang/Object.java Thu Oct 24 09:57:29 2019 +0200
@@ -38,11 +38,6 @@
*/
public class Object {
- private static native void registerNatives();
- static {
- registerNatives();
- }
-
/**
* Constructs a new object.
*/
--- a/src/java.base/share/native/libjava/Object.c Wed Oct 23 18:43:18 2019 -0700
+++ b/src/java.base/share/native/libjava/Object.c Thu Oct 24 09:57:29 2019 +0200
@@ -39,21 +39,6 @@
#include "java_lang_Object.h"
-static JNINativeMethod methods[] = {
- {"hashCode", "()I", (void *)&JVM_IHashCode},
- {"wait", "(J)V", (void *)&JVM_MonitorWait},
- {"notify", "()V", (void *)&JVM_MonitorNotify},
- {"notifyAll", "()V", (void *)&JVM_MonitorNotifyAll},
- {"clone", "()Ljava/lang/Object;", (void *)&JVM_Clone},
-};
-
-JNIEXPORT void JNICALL
-Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls)
-{
- (*env)->RegisterNatives(env, cls,
- methods, sizeof(methods)/sizeof(methods[0]));
-}
-
JNIEXPORT jclass JNICALL
Java_java_lang_Object_getClass(JNIEnv *env, jobject this)
{
--- a/test/hotspot/jtreg/compiler/dependencies/MonomorphicObjectCall/java.base/java/lang/Object.java Wed Oct 23 18:43:18 2019 -0700
+++ b/test/hotspot/jtreg/compiler/dependencies/MonomorphicObjectCall/java.base/java/lang/Object.java Thu Oct 24 09:57:29 2019 +0200
@@ -34,11 +34,6 @@
@HotSpotIntrinsicCandidate
public Object() {}
- private static native void registerNatives();
- static {
- registerNatives();
- }
-
@HotSpotIntrinsicCandidate
public final native Class<?> getClass();
--- a/test/hotspot/jtreg/runtime/8024804/RegisterNatives.java Wed Oct 23 18:43:18 2019 -0700
+++ b/test/hotspot/jtreg/runtime/8024804/RegisterNatives.java Thu Oct 24 09:57:29 2019 +0200
@@ -23,15 +23,27 @@
/*
* @test
- * @bug 8024804
- * @bug 8028741
+ * @bug 8024804 8028741 8232613
* @summary interface method resolution should skip finding j.l.Object's registerNatives() and succeed in selecting class B's registerNatives()
* @run main RegisterNatives
*/
public class RegisterNatives {
- interface I { void registerNatives(); }
+ interface I {
+ void registerNatives();
+ }
+
interface J extends I {}
- static class B implements J { public void registerNatives() { System.out.println("B"); } }
+
+ interface K {
+ default public void registerNatives() { System.out.println("K"); }
+ }
+
+ static class B implements J {
+ public void registerNatives() { System.out.println("B"); }
+ }
+
+ static class C implements K {}
+
public static void main(String... args) {
System.out.println("Regression test for JDK-8024804, crash when InterfaceMethodref resolves to Object.registerNatives\n");
J val = new B();
@@ -42,6 +54,14 @@
e.printStackTrace();
throw e;
}
+ C cval = new C();
+ try {
+ cval.registerNatives();
+ } catch (IllegalAccessError e) {
+ System.out.println("TEST FAILS - a default method named registerNatives should no longer be masked by removed Object.registerNatives\n");
+ e.printStackTrace();
+ throw e;
+ }
System.out.println("TEST PASSES - no IAE resulted\n");
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/serviceability/jvmti/RedefineClasses/RedefineObject.java Thu Oct 24 09:57:29 2019 +0200
@@ -0,0 +1,177 @@
+/*
+ * 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 8232613
+ * @summary Ensure Object natives stay registered after redefinition
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ * java.base/jdk.internal.org.objectweb.asm
+ * java.compiler
+ * java.instrument
+ * jdk.jartool/sun.tools.jar
+ * @run main RedefineObject buildagent
+ * @run main/othervm -javaagent:redefineagent.jar RedefineObject
+ */
+
+import static jdk.test.lib.Asserts.assertTrue;
+import java.io.FileNotFoundException;
+import java.io.PrintWriter;
+import java.lang.RuntimeException;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.IllegalClassFormatException;
+import java.lang.instrument.Instrumentation;
+import java.lang.instrument.UnmodifiableClassException;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+
+import jdk.internal.org.objectweb.asm.ClassReader;
+import jdk.internal.org.objectweb.asm.ClassVisitor;
+import jdk.internal.org.objectweb.asm.ClassWriter;
+
+import static jdk.internal.org.objectweb.asm.Opcodes.ASM6;
+import static jdk.internal.org.objectweb.asm.Opcodes.V1_8;
+
+public class RedefineObject {
+
+ static Instrumentation inst;
+
+ public static void premain(String agentArgs, Instrumentation inst) {
+ RedefineObject.inst = inst;
+ }
+
+ static class Transformer implements ClassFileTransformer {
+
+ public byte[] asm(ClassLoader loader, String className,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain, byte[] classfileBuffer)
+ throws IllegalClassFormatException {
+ ClassWriter cw = new ClassWriter(0);
+ // Force an older ASM to force a bytecode update
+ ClassVisitor cv = new DummyClassVisitor(ASM6, cw) { };
+ ClassReader cr = new ClassReader(classfileBuffer);
+ cr.accept(cv, 0);
+ byte[] bytes = cw.toByteArray();
+ return bytes;
+ }
+
+ public class DummyClassVisitor extends ClassVisitor {
+
+ public DummyClassVisitor(int api, ClassVisitor cv) {
+ super(api, cv);
+ }
+
+ public void visit(
+ final int version,
+ final int access,
+ final String name,
+ final String signature,
+ final String superName,
+ final String[] interfaces) {
+ // Artificially lower to JDK 8 version to force a redefine
+ cv.visit(V1_8, access, name, signature, superName, interfaces);
+ }
+ }
+
+ @Override public byte[] transform(ClassLoader loader, String className,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain, byte[] classfileBuffer)
+ throws IllegalClassFormatException {
+
+ if (className.contains("java/lang/Object")) {
+ try {
+ // Here we remove and re-add the dummy fields. This shuffles the constant pool
+ return asm(loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
+ } catch (Throwable e) {
+ // The retransform native code that called this method does not propagate
+ // exceptions. Instead of getting an uninformative generic error, catch
+ // problems here and print it, then exit.
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+ return null;
+ }
+ }
+
+ private static void buildAgent() {
+ try {
+ ClassFileInstaller.main("RedefineObject");
+ } catch (Exception e) {
+ throw new RuntimeException("Could not write agent classfile", e);
+ }
+
+ try {
+ PrintWriter pw = new PrintWriter("MANIFEST.MF");
+ pw.println("Premain-Class: RedefineObject");
+ pw.println("Agent-Class: RedefineObject");
+ pw.println("Can-Retransform-Classes: true");
+ pw.close();
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException("Could not write manifest file for the agent", e);
+ }
+
+ sun.tools.jar.Main jarTool = new sun.tools.jar.Main(System.out, System.err, "jar");
+ if (!jarTool.run(new String[] { "-cmf", "MANIFEST.MF", "redefineagent.jar", "RedefineObject.class" })) {
+ throw new RuntimeException("Could not write the agent jar file");
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+
+ int objHash = System.identityHashCode(Object.class);
+ System.out.println("Object hashCode: " + objHash);
+ if (args.length == 1 && args[0].equals("buildagent")) {
+ buildAgent();
+ return;
+ }
+
+ if (inst == null) {
+ throw new RuntimeException("Instrumentation object was null");
+ }
+
+ try {
+ inst.addTransformer(new RedefineObject.Transformer(), true);
+ inst.retransformClasses(Object.class);
+ } catch (UnmodifiableClassException e) {
+ throw new RuntimeException(e);
+ }
+
+ // Exercise native methods on Object after transform
+ Object b = new Object();
+ b.hashCode();
+
+ C c = new C();
+ assertTrue(c.hashCode() != c.clone().hashCode() || c != c.clone());
+ assertTrue(c.clone() instanceof C);
+ c = (C)c.clone(); // native method on new Object
+ }
+
+ private static class C implements Cloneable {
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+ }
+}
--- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/bcinstr/BI04/bi04t002/newclass02/java.base/java/lang/Object.java Wed Oct 23 18:43:18 2019 -0700
+++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/bcinstr/BI04/bi04t002/newclass02/java.base/java/lang/Object.java Thu Oct 24 09:57:29 2019 +0200
@@ -37,11 +37,6 @@
*/
public class Object {
- private static native void registerNatives();
- static {
- registerNatives();
- }
-
/**
* Returns the runtime class of an object. That <tt>Class</tt>
* object is the object that is locked by <tt>static synchronized</tt>