8212200: assert when shared java.lang.Object is redefined by JVMTI agent
Reviewed-by: dholmes, jiangli, hseigel, lfoltan, sspitsyn
This commit is contained in:
parent
02966a7b67
commit
93395f6a9e
src/hotspot/share
classfile
memory
prims
test
hotspot/jtreg
runtime/SharedArchiveFile/serviceability
testlibrary/jvmti
lib/jdk/test/lib/cds
@ -50,6 +50,7 @@
|
||||
#include "oops/oop.inline.hpp"
|
||||
#include "oops/symbol.hpp"
|
||||
#include "oops/typeArrayOop.inline.hpp"
|
||||
#include "prims/jvmtiExport.hpp"
|
||||
#include "prims/resolvedMethodTable.hpp"
|
||||
#include "runtime/fieldDescriptor.inline.hpp"
|
||||
#include "runtime/frame.inline.hpp"
|
||||
@ -125,7 +126,7 @@ static void compute_offset(int &dest_offset,
|
||||
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 @@ static void compute_offset(int &dest_offset,
|
||||
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 @@ static void compute_offset(int& dest_offset, InstanceKlass* ik,
|
||||
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 @@ bool java_lang_Class::restore_archived_mirror(Klass *k,
|
||||
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,19 @@ void JavaClasses::compute_hard_coded_offsets() {
|
||||
// Compute non-hard-coded field offsets of all the classes in this file
|
||||
void JavaClasses::compute_offsets() {
|
||||
if (UseSharedSpaces) {
|
||||
return; // field offsets are loaded from archive
|
||||
assert(JvmtiExport::is_early_phase() && !(JvmtiExport::should_post_class_file_load_hook() &&
|
||||
JvmtiExport::has_early_class_hook_env()),
|
||||
"JavaClasses::compute_offsets() must be called in early JVMTI phase.");
|
||||
// None of the classes used by the rest of this function can be replaced by
|
||||
// JMVTI ClassFileLoadHook.
|
||||
// We are safe to use the archived offsets, which have already been restored
|
||||
// by JavaClasses::serialize_offsets, without computing the offsets again.
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 +4364,7 @@ int InjectedField::compute_offset() {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@
|
||||
#include "oops/oop.inline.hpp"
|
||||
#include "oops/symbol.hpp"
|
||||
#include "oops/typeArrayKlass.hpp"
|
||||
#include "prims/jvmtiEnvBase.hpp"
|
||||
#include "prims/jvmtiExport.hpp"
|
||||
#include "prims/resolvedMethodTable.hpp"
|
||||
#include "prims/methodHandles.hpp"
|
||||
#include "runtime/arguments.hpp"
|
||||
@ -1489,8 +1489,7 @@ InstanceKlass* SystemDictionary::load_instance_class(Symbol* class_name, Handle
|
||||
!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 @@ void SystemDictionary::initialize(TRAPS) {
|
||||
// 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,19 @@ static const short wk_init_info[] = {
|
||||
0
|
||||
};
|
||||
|
||||
#ifdef ASSERT
|
||||
bool SystemDictionary::is_well_known_klass(Symbol* class_name) {
|
||||
int sid;
|
||||
for (int i = 0; (sid = wk_init_info[i]) != 0; i++) {
|
||||
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 +2014,8 @@ void SystemDictionary::resolve_wk_klasses_until(WKID limit_id, WKID &start_id, T
|
||||
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 +2083,8 @@ void SystemDictionary::resolve_preloaded_classes(TRAPS) {
|
||||
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 +2101,17 @@ void SystemDictionary::resolve_preloaded_classes(TRAPS) {
|
||||
Method* method = InstanceKlass::cast(ClassLoader_klass())->find_method(vmSymbols::checkPackageAccess_name(), vmSymbols::class_protectiondomain_signature());
|
||||
_has_checkPackageAccess = (method != NULL);
|
||||
}
|
||||
|
||||
#ifdef ASSERT
|
||||
if (UseSharedSpaces) {
|
||||
assert(JvmtiExport::is_early_phase(),
|
||||
"All well known classes must be resolved in JVMTI early phase");
|
||||
for (int i = FIRST_WKID; i < last; i++) {
|
||||
InstanceKlass* k = _well_known_klasses[i];
|
||||
assert(k->is_shared(), "must not be replaced by JVMTI class file load hook");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Tells if a given klass is a box (wrapper class, such as java.lang.Integer).
|
||||
|
@ -85,19 +85,20 @@ class ProtectionDomainCacheEntry;
|
||||
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
|
||||
// to provide a different definition.
|
||||
//
|
||||
// These klasses must all have names defined in vmSymbols.
|
||||
|
||||
#define WK_KLASS_ENUM_NAME(kname) kname##_knum
|
||||
|
||||
// Certain classes, such as java.lang.Object and java.lang.String,
|
||||
// are "well-known", in the sense that no class loader is allowed
|
||||
// to provide a different definition.
|
||||
//
|
||||
// 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.
|
||||
|
||||
//
|
||||
// The order of these definitions is significant: the classes are
|
||||
// resolved during early VM start-up by resolve_well_known_classes
|
||||
// in this order. Changing the order may require careful restructuring
|
||||
// of the VM start-up sequence.
|
||||
//
|
||||
#define WK_KLASSES_DO(do_klass) \
|
||||
/* well-known classes */ \
|
||||
do_klass(Object_klass, java_lang_Object ) \
|
||||
@ -127,7 +128,7 @@ class OopStorage;
|
||||
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 @@ class OopStorage;
|
||||
/* 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 @@ public:
|
||||
// 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 @@ public:
|
||||
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 @@ protected:
|
||||
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 @@ protected:
|
||||
InstanceKlass* k, Handle loader,
|
||||
TRAPS);
|
||||
|
||||
// Variables holding commonly used klasses (preloaded)
|
||||
static InstanceKlass* _well_known_klasses[];
|
||||
|
||||
// table of box klasses (int_klass, etc.)
|
||||
|
@ -914,6 +914,19 @@ void FileMapInfo::map_heap_regions_impl() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (JvmtiExport::should_post_class_file_load_hook() && JvmtiExport::has_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.
|
||||
//
|
||||
// If JvmtiExport::has_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 +1237,15 @@ bool FileMapInfo::_validating_shared_path_table = false;
|
||||
bool FileMapInfo::initialize() {
|
||||
assert(UseSharedSpaces, "UseSharedSpaces expected.");
|
||||
|
||||
if (JvmtiExport::should_post_class_file_load_hook() && JvmtiExport::has_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, so we can still use CDS if
|
||||
// JvmtiExport::has_early_class_hook_env() is false.
|
||||
FileMapInfo::fail_continue("CDS is disabled because early JVMTI ClassFileLoadHook is in use.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!open_for_read()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -417,6 +417,11 @@ void HeapShared::initialize_from_archived_subgraph(Klass* k) {
|
||||
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(THREAD);
|
||||
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()) {
|
||||
|
@ -994,6 +994,20 @@ class JvmtiClassFileLoadHookPoster : public StackObj {
|
||||
}
|
||||
};
|
||||
|
||||
bool JvmtiExport::is_early_phase() {
|
||||
return JvmtiEnvBase::get_phase() <= JVMTI_PHASE_PRIMORDIAL;
|
||||
}
|
||||
|
||||
bool JvmtiExport::has_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
|
||||
|
@ -328,6 +328,8 @@ class JvmtiExport : public AllStatic {
|
||||
JVMTI_ONLY(return _should_post_class_file_load_hook);
|
||||
NOT_JVMTI(return false;)
|
||||
}
|
||||
static bool is_early_phase();
|
||||
static bool has_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,
|
||||
|
222
test/hotspot/jtreg/runtime/SharedArchiveFile/serviceability/ReplaceCriticalClasses.java
Normal file
222
test/hotspot/jtreg/runtime/SharedArchiveFile/serviceability/ReplaceCriticalClasses.java
Normal file
@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright (c) 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
|
||||
* 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
|
||||
* @build sun.hotspot.WhiteBox
|
||||
* @run driver ClassFileInstaller -jar whitebox.jar sun.hotspot.WhiteBox
|
||||
* @run main/othervm/native ReplaceCriticalClasses
|
||||
*/
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import jdk.test.lib.cds.CDSTestUtils;
|
||||
import jdk.test.lib.cds.CDSOptions;
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
import sun.hotspot.WhiteBox;
|
||||
|
||||
public class ReplaceCriticalClasses {
|
||||
public static void main(String args[]) throws Throwable {
|
||||
if (args.length == 0) {
|
||||
launchChildProcesses();
|
||||
} else if (args.length == 3 && args[0].equals("child")) {
|
||||
Class klass = Class.forName(args[2].replace("/", "."));
|
||||
if (args[1].equals("-shared")) {
|
||||
testInChild(true, klass);
|
||||
} else if (args[1].equals("-notshared")) {
|
||||
testInChild(false, klass);
|
||||
} else {
|
||||
throw new RuntimeException("Unknown child exec option " + args[1]);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
throw new RuntimeException("Usage: @run main/othervm/native ReplaceCriticalClasses");
|
||||
}
|
||||
}
|
||||
|
||||
static void launchChildProcesses() throws Throwable {
|
||||
String tests[] = {
|
||||
// CDS should be disabled -- these critical classes will be replaced
|
||||
// because JvmtiExport::early_class_hook_env() is true.
|
||||
"-early -notshared java/lang/Object",
|
||||
"-early -notshared java/lang/String",
|
||||
"-early -notshared java/lang/Cloneable",
|
||||
"-early -notshared java/io/Serializable",
|
||||
|
||||
// CDS should not be disabled -- these critical classes cannot be replaced because
|
||||
// JvmtiExport::early_class_hook_env() is false.
|
||||
"java/lang/Object",
|
||||
"java/lang/String",
|
||||
"java/lang/Cloneable",
|
||||
"java/io/Serializable",
|
||||
|
||||
// Try to replace classes that are used by the archived subgraph graphs.
|
||||
"-subgraph java/util/ArrayList",
|
||||
"-subgraph java/lang/module/ResolvedModule",
|
||||
|
||||
// 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.
|
||||
"-notshared jdk/internal/vm/PostVMInitHook",
|
||||
"-notshared java/util/Locale",
|
||||
"-notshared sun/util/locale/BaseLocale",
|
||||
"-notshared java/lang/Readable",
|
||||
};
|
||||
|
||||
int n = 0;
|
||||
for (String s : tests) {
|
||||
System.out.println("Test case[" + (n++) + "] = \"" + s + "\"");
|
||||
String args[] = s.split("\\s+"); // split by space character
|
||||
launchChild(args);
|
||||
}
|
||||
}
|
||||
|
||||
static void launchChild(String args[]) throws Throwable {
|
||||
if (args.length < 1) {
|
||||
throw new RuntimeException("Invalid test case. Should be <-early> <-subgraph> <-notshared> klassName");
|
||||
}
|
||||
String klassName = null;
|
||||
String early = "";
|
||||
boolean subgraph = false;
|
||||
String shared = "-shared";
|
||||
|
||||
for (int i=0; i<args.length-1; i++) {
|
||||
String opt = args[i];
|
||||
if (opt.equals("-early")) {
|
||||
early = "-early,";
|
||||
} else if (opt.equals("-subgraph")) {
|
||||
subgraph = true;
|
||||
} else if (opt.equals("-notshared")) {
|
||||
shared = opt;
|
||||
} else {
|
||||
throw new RuntimeException("Unknown option: " + opt);
|
||||
}
|
||||
}
|
||||
klassName = args[args.length-1];
|
||||
Class.forName(klassName.replace("/", ".")); // make sure it's a valid class
|
||||
|
||||
// We will pass an option like "-agentlib:SimpleClassFileLoadHook=java/util/Locale,XXX,XXX".
|
||||
// The SimpleClassFileLoadHook agent would attempt to hook the java/util/Locale class
|
||||
// but leave the class file bytes unchanged (it replaces all bytes "XXX" with "XXX", i.e.,
|
||||
// a no-op). JVMTI doesn't check the class file bytes returned by the agent, so as long
|
||||
// as the agent returns a buffer, it will not load the class from CDS, and will instead
|
||||
// load the class by parsing the buffer.
|
||||
//
|
||||
// Note that for safety we don't change the contents of the class file bytes. If in the
|
||||
// future JVMTI starts checking the contents of the class file bytes, this test would need
|
||||
// to be updated. (You'd see the test case with java/util/Locale staring to fail).
|
||||
String agent = "-agentlib:SimpleClassFileLoadHook=" + early + klassName + ",XXX,XXX";
|
||||
|
||||
CDSOptions opts = (new CDSOptions())
|
||||
.setXShareMode("auto")
|
||||
.setUseSystemArchive(true)
|
||||
.setUseVersion(false)
|
||||
.addSuffix("-showversion",
|
||||
"-Xlog:cds",
|
||||
"-XX:+UnlockDiagnosticVMOptions",
|
||||
agent,
|
||||
"-XX:+WhiteBoxAPI",
|
||||
"-Xbootclasspath/a:" + ClassFileInstaller.getJarPath("whitebox.jar"));
|
||||
|
||||
if (subgraph) {
|
||||
opts.addSuffix("-Xlog:cds+heap",
|
||||
"-Xlog:class+load");
|
||||
}
|
||||
|
||||
opts.addSuffix("ReplaceCriticalClasses",
|
||||
"child",
|
||||
shared,
|
||||
klassName);
|
||||
|
||||
final boolean expectDisable = !early.equals("");
|
||||
final boolean checkSubgraph = subgraph;
|
||||
CDSTestUtils.run(opts).assertNormalExit(out -> {
|
||||
if (expectDisable) {
|
||||
out.shouldContain("UseSharedSpaces: CDS is disabled because early JVMTI ClassFileLoadHook is in use.");
|
||||
System.out.println("CDS disabled as expected");
|
||||
}
|
||||
if (checkSubgraph) {
|
||||
// As of 2018/10/21 the classes in the archived subgraphs won't be
|
||||
// replaced because all archived subgraphs were loaded in JVMTI_PHASE_PRIMORDIAL.
|
||||
//
|
||||
// This is the first class to be loaded after JVMTI has exited JVMTI_PHASE_PRIMORDIAL.
|
||||
// Make sure no subgraphs are loaded afterwards.
|
||||
//
|
||||
// Can't use out.shouldNotMatch() because that doesn't match across multiple lines.
|
||||
String firstNonPrimordialClass = "jdk.jfr.internal.EventWriter";
|
||||
String regexp = firstNonPrimordialClass + ".*initialize_from_archived_subgraph";
|
||||
Pattern regex = Pattern.compile(regexp, Pattern.DOTALL);
|
||||
Matcher matcher = regex.matcher(out.getStdout());
|
||||
if (matcher.find()) {
|
||||
out.reportDiagnosticSummary();
|
||||
throw new RuntimeException("'" + regexp
|
||||
+ "' found in stdout: '" + matcher.group() + "' \n");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void testInChild(boolean shouldBeShared, Class klass) {
|
||||
WhiteBox wb = WhiteBox.getWhiteBox();
|
||||
|
||||
if (shouldBeShared && !wb.isSharedClass(klass)) {
|
||||
throw new RuntimeException(klass + " should be shared but but actually is not.");
|
||||
}
|
||||
if (!shouldBeShared && wb.isSharedClass(klass)) {
|
||||
throw new RuntimeException(klass + " should not be shared but actually is.");
|
||||
}
|
||||
System.out.println("wb.isSharedClass(klass): " + wb.isSharedClass(klass) + " == " + shouldBeShared);
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
@ -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 @@ ClassFileLoadHook(jvmtiEnv *jvmti_env, JNIEnv *env, jclass class_beeing_redefine
|
||||
*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 init_options(char *options) {
|
||||
|
||||
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 @@ static jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) {
|
||||
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) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2017, 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
|
||||
@ -31,6 +31,7 @@ public class CDSOptions {
|
||||
public String archiveName;
|
||||
public ArrayList<String> prefix = new ArrayList<String>();
|
||||
public ArrayList<String> suffix = new ArrayList<String>();
|
||||
public boolean useSystemArchive = false;
|
||||
|
||||
// Indicate whether to append "-version" when using CDS Archive.
|
||||
// Most of tests will use '-version'
|
||||
@ -68,4 +69,9 @@ public class CDSOptions {
|
||||
this.useVersion = use;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CDSOptions setUseSystemArchive(boolean use) {
|
||||
this.useSystemArchive = use;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -82,21 +82,21 @@ public class CDSTestUtils {
|
||||
*
|
||||
* Instead, the test case should be written as
|
||||
*
|
||||
* CCDSTestUtils.run(args).assertNormalExit("Hi");
|
||||
* CDSTestUtils.run(args).assertNormalExit("Hi");
|
||||
*
|
||||
* EXAMPLES/HOWTO
|
||||
*
|
||||
* 1. For simple substring matching:
|
||||
*
|
||||
* CCDSTestUtils.run(args).assertNormalExit("Hi");
|
||||
* CCDSTestUtils.run(args).assertNormalExit("a", "b", "x");
|
||||
* CCDSTestUtils.run(args).assertAbnormalExit("failure 1", "failure2");
|
||||
* CDSTestUtils.run(args).assertNormalExit("Hi");
|
||||
* CDSTestUtils.run(args).assertNormalExit("a", "b", "x");
|
||||
* CDSTestUtils.run(args).assertAbnormalExit("failure 1", "failure2");
|
||||
*
|
||||
* 2. For more complex output matching: using Lambda expressions
|
||||
*
|
||||
* CCDSTestUtils.run(args)
|
||||
* CDSTestUtils.run(args)
|
||||
* .assertNormalExit(output -> output.shouldNotContain("this should not be printed");
|
||||
* CCDSTestUtils.run(args)
|
||||
* CDSTestUtils.run(args)
|
||||
* .assertAbnormalExit(output -> {
|
||||
* output.shouldNotContain("this should not be printed");
|
||||
* output.shouldHaveExitValue(123);
|
||||
@ -104,13 +104,13 @@ public class CDSTestUtils {
|
||||
*
|
||||
* 3. Chaining several checks:
|
||||
*
|
||||
* CCDSTestUtils.run(args)
|
||||
* CDSTestUtils.run(args)
|
||||
* .assertNormalExit(output -> output.shouldNotContain("this should not be printed")
|
||||
* .assertNormalExit("should have this", "should have that");
|
||||
*
|
||||
* 4. [Rare use case] if a test sometimes exit normally, and sometimes abnormally:
|
||||
*
|
||||
* CCDSTestUtils.run(args)
|
||||
* CDSTestUtils.run(args)
|
||||
* .ifNormalExit("ths string is printed when exiting with 0")
|
||||
* .ifAbNormalExit("ths string is printed when exiting with 1");
|
||||
*
|
||||
@ -388,9 +388,11 @@ public class CDSTestUtils {
|
||||
cmd.add("-Xshare:" + opts.xShareMode);
|
||||
cmd.add("-Dtest.timeout.factor=" + TestTimeoutFactor);
|
||||
|
||||
if (opts.archiveName == null)
|
||||
opts.archiveName = getDefaultArchiveName();
|
||||
cmd.add("-XX:SharedArchiveFile=" + opts.archiveName);
|
||||
if (!opts.useSystemArchive) {
|
||||
if (opts.archiveName == null)
|
||||
opts.archiveName = getDefaultArchiveName();
|
||||
cmd.add("-XX:SharedArchiveFile=" + opts.archiveName);
|
||||
}
|
||||
|
||||
if (opts.useVersion)
|
||||
cmd.add("-version");
|
||||
|
Loading…
x
Reference in New Issue
Block a user