--- old/src/hotspot/share/classfile/javaClasses.cpp 2018-10-21 17:55:02.351117730 -0700 +++ new/src/hotspot/share/classfile/javaClasses.cpp 2018-10-21 17:55:02.067106815 -0700 @@ -50,6 +50,7 @@ #include "oops/oop.inline.hpp" #include "oops/symbol.hpp" #include "oops/typeArrayOop.inline.hpp" +#include "prims/jvmtiEnvBase.hpp" #include "prims/resolvedMethodTable.hpp" #include "runtime/fieldDescriptor.inline.hpp" #include "runtime/frame.inline.hpp" @@ -125,7 +126,7 @@ if (ik == NULL) { ResourceMark rm; log_error(class)("Mismatch JDK version for field: %s type: %s", name_symbol->as_C_string(), signature_symbol->as_C_string()); - vm_exit_during_initialization("Invalid layout of preloaded class"); + vm_exit_during_initialization("Invalid layout of well-known class"); } if (!ik->find_local_field(name_symbol, signature_symbol, &fd) || fd.is_static() != is_static) { @@ -138,7 +139,7 @@ LogStream ls(lt.error()); ik->print_on(&ls); #endif //PRODUCT - vm_exit_during_initialization("Invalid layout of preloaded class: use -Xlog:class+load=info to see the origin of the problem class"); + vm_exit_during_initialization("Invalid layout of well-known class: use -Xlog:class+load=info to see the origin of the problem class"); } dest_offset = fd.offset(); } @@ -151,7 +152,7 @@ if (name == NULL) { ResourceMark rm; log_error(class)("Name %s should be in the SymbolTable since its class is loaded", name_string); - vm_exit_during_initialization("Invalid layout of preloaded class", ik->external_name()); + vm_exit_during_initialization("Invalid layout of well-known class", ik->external_name()); } compute_offset(dest_offset, ik, name, signature_symbol, is_static); } @@ -1196,7 +1197,7 @@ Handle class_loader, Handle module, Handle protection_domain, TRAPS) { // Postpone restoring archived mirror until java.lang.Class is loaded. Please - // see more details in SystemDictionary::resolve_preloaded_classes(). + // see more details in SystemDictionary::resolve_well_known_classes(). if (!SystemDictionary::Class_klass_loaded()) { assert(fixup_mirror_list() != NULL, "fixup_mirror_list not initialized"); fixup_mirror_list()->push(k); @@ -4250,12 +4251,14 @@ // Compute non-hard-coded field offsets of all the classes in this file void JavaClasses::compute_offsets() { if (UseSharedSpaces) { + assert(JvmtiEnvBase::get_phase() <= JVMTI_PHASE_PRIMORDIAL, + "Field offsets of well-known classes must be computed in JVMTI_PHASE_PRIMORDIAL or before"); return; // field offsets are loaded from archive } // We have already called the compute_offsets() of the // BASIC_JAVA_CLASSES_DO_PART1 classes (java_lang_String and java_lang_Class) - // earlier inside SystemDictionary::resolve_preloaded_classes() + // earlier inside SystemDictionary::resolve_well_known_classes() BASIC_JAVA_CLASSES_DO_PART2(DO_COMPUTE_OFFSETS); // generated interpreter code wants to know about the offsets we just computed: @@ -4356,7 +4359,7 @@ tty->print_cr(" name: %s, sig: %s, flags: %08x", fs.name()->as_C_string(), fs.signature()->as_C_string(), fs.access_flags().as_int()); } #endif //PRODUCT - vm_exit_during_initialization("Invalid layout of preloaded class: use -Xlog:class+load=info to see the origin of the problem class"); + vm_exit_during_initialization("Invalid layout of well-known class: use -Xlog:class+load=info to see the origin of the problem class"); return -1; } --- old/src/hotspot/share/classfile/systemDictionary.cpp 2018-10-21 17:55:03.071145403 -0700 +++ new/src/hotspot/share/classfile/systemDictionary.cpp 2018-10-21 17:55:02.791134641 -0700 @@ -1489,8 +1489,7 @@ !search_only_bootloader_append, "Attempt to load a class outside of boot loader's module path"); - // Search the shared system dictionary for classes preloaded into the - // shared spaces. + // Search for classes in the CDS archive. InstanceKlass* k = NULL; { #if INCLUDE_CDS @@ -1958,7 +1957,7 @@ // Allocate private object used as system class loader lock _system_loader_lock_obj = oopFactory::new_intArray(0, CHECK); // Initialize basic classes - resolve_preloaded_classes(CHECK); + resolve_well_known_classes(CHECK); } // Compact table of directions on the initialization of klasses: @@ -1971,6 +1970,22 @@ 0 }; +#ifdef ASSERT +bool SystemDictionary::is_well_known_klass(Symbol* class_name) { + for (int i = 0; ; i++) { + int sid = wk_init_info[i]; + if (sid == 0) { + break; + } + Symbol* symbol = vmSymbols::symbol_at((vmSymbols::SID)sid); + if (class_name == symbol) { + return true; + } + } + return false; +} +#endif + bool SystemDictionary::resolve_wk_klass(WKID id, TRAPS) { assert(id >= (int)FIRST_WKID && id < (int)WKID_LIMIT, "oob"); int sid = wk_init_info[id - FIRST_WKID]; @@ -2002,8 +2017,8 @@ start_id = limit_id; } -void SystemDictionary::resolve_preloaded_classes(TRAPS) { - assert(WK_KLASS(Object_klass) == NULL, "preloaded classes should only be initialized once"); +void SystemDictionary::resolve_well_known_classes(TRAPS) { + assert(WK_KLASS(Object_klass) == NULL, "well-known classes should only be initialized once"); // Create the ModuleEntry for java.base. This call needs to be done here, // after vmSymbols::initialize() is called but before any classes are pre-loaded. @@ -2071,7 +2086,8 @@ WKID jsr292_group_end = WK_KLASS_ENUM_NAME(VolatileCallSite_klass); resolve_wk_klasses_until(jsr292_group_start, scan, CHECK); resolve_wk_klasses_through(jsr292_group_end, scan, CHECK); - resolve_wk_klasses_until(NOT_JVMCI(WKID_LIMIT) JVMCI_ONLY(FIRST_JVMCI_WKID), scan, CHECK); + WKID last = NOT_JVMCI(WKID_LIMIT) JVMCI_ONLY(FIRST_JVMCI_WKID); + resolve_wk_klasses_until(last, scan, CHECK); _box_klasses[T_BOOLEAN] = WK_KLASS(Boolean_klass); _box_klasses[T_CHAR] = WK_KLASS(Character_klass); @@ -2088,6 +2104,15 @@ Method* method = InstanceKlass::cast(ClassLoader_klass())->find_method(vmSymbols::checkPackageAccess_name(), vmSymbols::class_protectiondomain_signature()); _has_checkPackageAccess = (method != NULL); } + + if (UseSharedSpaces) { + assert(JvmtiEnvBase::get_phase() <= JVMTI_PHASE_PRIMORDIAL, + "All well known classes must be resolved in JVMTI_PHASE_PRIMORDIAL or before"); + for (int i=FIRST_WKID; iis_shared(), "must not be replaced by JVMTI class file load hook"); + } + } } // Tells if a given klass is a box (wrapper class, such as java.lang.Integer). --- old/src/hotspot/share/classfile/systemDictionary.hpp 2018-10-21 17:55:03.775172460 -0700 +++ new/src/hotspot/share/classfile/systemDictionary.hpp 2018-10-21 17:55:03.491161545 -0700 @@ -85,18 +85,19 @@ class GCTimer; class OopStorage; -// Certain classes are preloaded, such as java.lang.Object and java.lang.String. -// They are all "well-known", in the sense that no class loader is allowed +// Certain classes, such as java.lang.Object and java.lang.String, +// are all "well-known", in the sense that no class loader is allowed // to provide a different definition. // -// These klasses must all have names defined in vmSymbols. +// These klasses must all have names defined in vmSymbols. They are +// resolved during early VM start-up in a specific order. #define WK_KLASS_ENUM_NAME(kname) kname##_knum // Each well-known class has a short klass name (like object_klass), // and a vmSymbol name (like java_lang_Object). // The order of these definitions is significant; it is the order in which -// preloading is actually performed by resolve_preloaded_classes. +// class resolution is actually performed by resolve_well_known_classes. #define WK_KLASSES_DO(do_klass) \ /* well-known classes */ \ @@ -127,7 +128,7 @@ do_klass(IllegalMonitorStateException_klass, java_lang_IllegalMonitorStateException ) \ do_klass(Reference_klass, java_lang_ref_Reference ) \ \ - /* Preload ref klasses and set reference types */ \ + /* ref klasses and set reference types */ \ do_klass(SoftReference_klass, java_lang_ref_SoftReference ) \ do_klass(WeakReference_klass, java_lang_ref_WeakReference ) \ do_klass(FinalReference_klass, java_lang_ref_FinalReference ) \ @@ -200,7 +201,7 @@ /* support for stack dump lock analysis */ \ do_klass(java_util_concurrent_locks_AbstractOwnableSynchronizer_klass, java_util_concurrent_locks_AbstractOwnableSynchronizer) \ \ - /* Preload boxing klasses */ \ + /* boxing klasses */ \ do_klass(Boolean_klass, java_lang_Boolean ) \ do_klass(Character_klass, java_lang_Character ) \ do_klass(Float_klass, java_lang_Float ) \ @@ -391,7 +392,8 @@ // Initialization static void initialize(TRAPS); - // Checked fast access to commonly used classes - mostly preloaded + // Checked fast access to the well-known classes -- so that you don't try to use them + // before they are resolved. static InstanceKlass* check_klass(InstanceKlass* k) { assert(k != NULL, "klass not loaded"); return k; @@ -435,6 +437,12 @@ return check_klass(_box_klasses[t]); } static BasicType box_klass_type(Klass* k); // inverse of box_klass +#ifdef ASSERT + static bool is_well_known_klass(Klass* k) { + return is_well_known_klass(k->name()); + } + static bool is_well_known_klass(Symbol* class_name); +#endif protected: // Returns the class loader data to be used when looking up/updating the @@ -695,8 +703,8 @@ ClassLoaderData* loader_data, TRAPS); - // Resolve preloaded classes so they can be used like SystemDictionary::String_klass() - static void resolve_preloaded_classes(TRAPS); + // Resolve well_known classes so they can be used like SystemDictionary::String_klass() + static void resolve_well_known_classes(TRAPS); // Class loader constraints static void check_constraints(unsigned int hash, @@ -707,7 +715,6 @@ InstanceKlass* k, Handle loader, TRAPS); - // Variables holding commonly used klasses (preloaded) static InstanceKlass* _well_known_klasses[]; // table of box klasses (int_klass, etc.) --- old/src/hotspot/share/memory/filemap.cpp 2018-10-21 17:55:04.459198748 -0700 +++ new/src/hotspot/share/memory/filemap.cpp 2018-10-21 17:55:04.179187987 -0700 @@ -914,6 +914,20 @@ return; } + if (JvmtiExport::should_post_class_file_load_hook() && JvmtiExport::early_class_hook_env()) { + ShouldNotReachHere(); // CDS should have been disabled. + // The archived objects are mapped at JVM start-up, but we don't know if + // j.l.String or j.l.Class might be replaced by the ClassFileLoadHook, + // which would make the archived String or mirror objects invalid. Let's be safe and not + // use the archived objects. These 2 classes are loaded during the JVMTI "early" stage + // (aka JVMTI_PHASE_PRIMORDIAL). + // + // If JvmtiExport::early_class_hook_env() is false, the classes of some objects + // in the archived subgraphs may be replaced by the ClassFileLoadHook. But that's OK + // because we won't install an archived object subgraph if the klass of any of the + // referenced objects are replaced. See HeapShared::initialize_from_archived_subgraph(). + } + MemRegion heap_reserved = Universe::heap()->reserved_region(); log_info(cds)("CDS archive was created with max heap size = " SIZE_FORMAT "M, and the following configuration:", @@ -1224,6 +1238,15 @@ bool FileMapInfo::initialize() { assert(UseSharedSpaces, "UseSharedSpaces expected."); + if (JvmtiExport::should_post_class_file_load_hook() && JvmtiExport::early_class_hook_env()) { + // CDS assumes that no classes resolved in SystemDictionary::resolve_well_known_classes + // are replaced at runtime by JVMTI ClassFileLoadHook. All of those classes are resolved + // during the JVMTI "early" stage (JVMTI_PHASE_PRIMORDIAL), so we're OK if + // JvmtiExport::early_class_hook_env() is not requested by native agent(s). + FileMapInfo::fail_continue("CDS is disabled because early JVMTI ClassFileLoadHook is in use."); + return false; + } + if (!open_for_read()) { return false; } --- old/src/hotspot/share/memory/heapShared.cpp 2018-10-21 17:55:05.135224731 -0700 +++ new/src/hotspot/share/memory/heapShared.cpp 2018-10-21 17:55:04.863214276 -0700 @@ -417,6 +417,11 @@ Klass* resolved_k = SystemDictionary::resolve_or_null( (obj_k)->name(), THREAD); if (resolved_k != obj_k) { + assert(!SystemDictionary::is_well_known_klass(resolved_k), + "shared well-known classes must not be replaced by JVMTI ClassFileLoadHook"); + ResourceMark rm; + log_info(cds, heap)("Failed to load subgraph because %s was not loaded from archive", + resolved_k->external_name()); return; } if ((obj_k)->is_instance_klass()) { --- old/src/hotspot/share/prims/jvmtiExport.cpp 2018-10-21 17:55:05.807250557 -0700 +++ new/src/hotspot/share/prims/jvmtiExport.cpp 2018-10-21 17:55:05.527239796 -0700 @@ -994,6 +994,16 @@ } }; +bool JvmtiExport::early_class_hook_env() { + JvmtiEnvIterator it; + for (JvmtiEnv* env = it.first(); env != NULL; env = it.next(env)) { + if (env->early_class_hook_env()) { + return true; + } + } + return false; +} + bool JvmtiExport::_should_post_class_file_load_hook = false; // this entry is for class file load hook on class load, redefine and retransform --- old/src/hotspot/share/prims/jvmtiExport.hpp 2018-10-21 17:55:06.499277154 -0700 +++ new/src/hotspot/share/prims/jvmtiExport.hpp 2018-10-21 17:55:06.219266392 -0700 @@ -328,6 +328,7 @@ JVMTI_ONLY(return _should_post_class_file_load_hook); NOT_JVMTI(return false;) } + static bool early_class_hook_env(); // Return true if the class was modified by the hook. static bool post_class_file_load_hook(Symbol* h_name, Handle class_loader, Handle h_protection_domain, --- old/test/hotspot/jtreg/testlibrary/jvmti/libSimpleClassFileLoadHook.c 2018-10-21 17:55:07.167302827 -0700 +++ new/test/hotspot/jtreg/testlibrary/jvmti/libSimpleClassFileLoadHook.c 2018-10-21 17:55:06.887292067 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2018, 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 @@ -91,17 +91,23 @@ *new_class_data_len = class_data_len; *new_class_data = new_data; - fprintf(stderr, "Rewriting done. Replaced %d occurrence(s)\n", count); + fprintf(stderr, "Rewriting done. Replaced %d occurrence(s) of \"%s\" to \"%s\"\n", count, FROM, TO); } } } +static int early = 0; + static jint init_options(char *options) { char* class_name; char* from; char* to; fprintf(stderr, "Agent library loaded with options = %s\n", options); + if (options != NULL && strncmp(options, "+early,", 7) == 0) { + early = 1; + options += 7; + } if ((class_name = options) != NULL && (from = strchr(class_name, ',')) != NULL && (from[1] != 0)) { *from = 0; @@ -132,6 +138,7 @@ static jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) { int rc; + jvmtiCapabilities caps; if ((rc = (*jvm)->GetEnv(jvm, (void **)&jvmti, JVMTI_VERSION_1_1)) != JNI_OK) { fprintf(stderr, "Unable to create jvmtiEnv, GetEnv failed, error = %d\n", rc); @@ -141,6 +148,19 @@ return JNI_ERR; } + memset(&caps, 0, sizeof(caps)); + + caps.can_redefine_classes = 1; + if (early) { + fprintf(stderr, "can_generate_all_class_hook_events/can_generate_early_vmstart/can_generate_early_class_hook_events == 1\n"); + caps.can_generate_all_class_hook_events = 1; + caps.can_generate_early_class_hook_events = 1; + } + if ((rc = (*jvmti)->AddCapabilities(jvmti, &caps)) != JNI_OK) { + fprintf(stderr, "AddCapabilities failed, error = %d\n", rc); + return JNI_ERR; + } + (void) memset(&callbacks, 0, sizeof(callbacks)); callbacks.ClassFileLoadHook = &ClassFileLoadHook; if ((rc = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(callbacks))) != JNI_OK) { --- /dev/null 2018-08-14 18:25:04.628316445 -0700 +++ new/test/hotspot/jtreg/runtime/SharedArchiveFile/serviceability/ReplaceCriticalClasses.java 2018-10-21 17:55:07.559317893 -0700 @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2016, 2017, 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 + * @summary Tests how CDS works when critical library classes are replaced with JVMTI ClassFileLoadHook + * @library /test/lib + * @requires vm.cds + * + * @comment CDS should not be disabled -- these critical classes will be replaced because JvmtiExport::early_class_hook_env() is true. + * @run main/othervm/native -Xshare:auto -agentlib:SimpleClassFileLoadHook=+early,java/lang/Object,XXX,XXX -showversion ReplaceCriticalClasses + * @run main/othervm/native -Xshare:auto -agentlib:SimpleClassFileLoadHook=+early,java/lang/String,XXX,XXX -showversion ReplaceCriticalClasses + * @run main/othervm/native -Xshare:auto -agentlib:SimpleClassFileLoadHook=+early,java/lang/Cloneable,XXX,XXX -showversion ReplaceCriticalClasses + * @run main/othervm/native -Xshare:auto -agentlib:SimpleClassFileLoadHook=+early,java/io/Serializable,XXX,XXX -showversion ReplaceCriticalClasses + * + * @comment CDS should not be disabled -- these critical classes cannot be replaced because JvmtiExport::early_class_hook_env() is false. + * @run main/othervm/native -Xshare:auto -agentlib:SimpleClassFileLoadHook=java/lang/Object,XXX,XXX -showversion ReplaceCriticalClasses + * @run main/othervm/native -Xshare:auto -agentlib:SimpleClassFileLoadHook=java/lang/String,XXX,XXX -showversion ReplaceCriticalClasses + * @run main/othervm/native -Xshare:auto -agentlib:SimpleClassFileLoadHook=java/lang/Cloneable,XXX,XXX -showversion ReplaceCriticalClasses + * @run main/othervm/native -Xshare:auto -agentlib:SimpleClassFileLoadHook=java/io/Serializable,XXX,XXX -showversion ReplaceCriticalClasses + * + * @comment Try to replace classes that are used by the archived subgraph graphs. As of 2018/10/21 the classes won't be replaced because + * all archived subgraphs were loaded in JVMTI_PHASE_PRIMORDIAL. + * @run main/othervm/native -Xshare:auto -agentlib:SimpleClassFileLoadHook=java/util/ArrayList,XXX,XXX -verbose -showversion -Xlog:cds+heap=debug ReplaceCriticalClasses + * @run main/othervm/native -Xshare:auto -agentlib:SimpleClassFileLoadHook=java/lang/module/ResolvedModule,XXX,XXX -verbose -showversion -Xlog:cds+heap=debug ReplaceCriticalClasses + * + * @comment Replace classes that are loaded after JVMTI_PHASE_PRIMORDIAL. It's OK to replace such classes even when CDS is enabled. + * Nothing bad should happen. + * @run main/othervm/native -Xshare:auto -agentlib:SimpleClassFileLoadHook=jdk/internal/vm/PostVMInitHook,XXX,XXX -showversion ReplaceCriticalClasses + * @run main/othervm/native -Xshare:auto -agentlib:SimpleClassFileLoadHook=java/util/Locale,XXX,XXX -showversion ReplaceCriticalClasses + * @run main/othervm/native -Xshare:auto -agentlib:SimpleClassFileLoadHook=sun/util/locale/BaseLocale,XXX,XXX -showversion ReplaceCriticalClasses + * @run main/othervm/native -Xshare:auto -agentlib:SimpleClassFileLoadHook=java/lang/Readable,XXX,XXX -showversion ReplaceCriticalClasses + */ + +public class ReplaceCriticalClasses { + public static void main(String args[]) { + String strings[] = { + // interned strings from j.l.Object + "@", + "nanosecond timeout value out of range", + "timeoutMillis value is negative", + + // interned strings from j.l.Integer + "0", + "0X", + "0x", + "int" + }; + + // Make sure the interned string table is same + for (String s : strings) { + String i = s.intern(); + if (s != i) { + throw new RuntimeException("Interned string mismatch: \"" + s + "\" @ " + System.identityHashCode(s) + + " vs \"" + i + "\" @ " + System.identityHashCode(i)); + } + } + // We have tried to use ClassFileLoadHook to replace critical library classes (which may + // may not have succeeded, depending on whether the agent has requested + // can_generate_all_class_hook_events/can_generate_early_class_hook_events capabilities). + // + // In any case, the JVM should have started properly (perhaps with CDS disabled) and + // the above operations should succeed. + System.out.println("If I can come to here without crashing, things should be OK"); + } +} \ No newline at end of file