From 85715e1050fa774c3267dbbe2f749717aeeec8ff Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Wed, 10 Sep 2025 19:21:00 +0000 Subject: [PATCH] 8317269: Store old classes in linked state in AOT cache Reviewed-by: coleenp, matsaave --- src/hotspot/share/cds/aotMetaspace.cpp | 20 +- src/hotspot/share/cds/aotMetaspace.hpp | 1 + src/hotspot/share/cds/archiveBuilder.cpp | 2 +- src/hotspot/share/cds/cdsConfig.cpp | 30 ++ src/hotspot/share/cds/cdsConfig.hpp | 9 + src/hotspot/share/cds/dumpTimeClassInfo.cpp | 13 +- src/hotspot/share/cds/dumpTimeClassInfo.hpp | 15 +- src/hotspot/share/cds/dynamicArchive.cpp | 6 + .../share/cds/lambdaProxyClassDictionary.cpp | 6 +- src/hotspot/share/cds/runTimeClassInfo.cpp | 10 +- src/hotspot/share/cds/runTimeClassInfo.hpp | 4 +- .../classfile/systemDictionaryShared.cpp | 316 +++++++++++++++--- .../classfile/systemDictionaryShared.hpp | 21 +- src/hotspot/share/oops/instanceKlass.cpp | 14 +- src/hotspot/share/oops/methodData.cpp | 5 +- src/hotspot/share/oops/trainingData.cpp | 9 +- src/hotspot/share/prims/jvm.cpp | 7 + src/hotspot/share/runtime/mutexLocker.cpp | 4 +- test/hotspot/jtreg/TEST.groups | 6 +- .../cds/appcds/aotCache/ExcludedClasses.java | 12 +- .../runtime/cds/appcds/aotCache/OldA.jasm | 38 +++ .../cds/appcds/aotCache/OldClassSupport.java | 162 +++++++++ ...dClassWithExcludedVerifierConstraints.jasm | 50 +++ .../OldClassWithVerifierConstraints.jasm | 50 +++ .../AOTClassLinkingVerification.java | 294 ++++++++++++++++ .../appcds/aotClassLinking/BadNewClass.jasm | 52 +++ .../appcds/aotClassLinking/BadNewClass2.jasm | 52 +++ .../appcds/aotClassLinking/BadNewClass3.jasm | 53 +++ .../appcds/aotClassLinking/BadNewClass4.jasm | 53 +++ .../appcds/aotClassLinking/BadOldClass.jasm | 52 +++ .../appcds/aotClassLinking/BadOldClass2.jasm | 52 +++ .../appcds/aotClassLinking/BadOldClass3.jasm | 53 +++ .../appcds/aotClassLinking/BadOldClass4.jasm | 53 +++ .../aotClassLinking/BulkLoaderTest.java | 2 +- .../appcds/aotClassLinking/GoodOldClass.jasm | 49 +++ 35 files changed, 1485 insertions(+), 90 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldA.jasm create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldClassSupport.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldClassWithExcludedVerifierConstraints.jasm create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldClassWithVerifierConstraints.jasm create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTClassLinkingVerification.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadNewClass.jasm create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadNewClass2.jasm create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadNewClass3.jasm create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadNewClass4.jasm create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadOldClass.jasm create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadOldClass2.jasm create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadOldClass3.jasm create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadOldClass4.jasm create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/GoodOldClass.jasm diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index c2d9d0193f5..cca82bed4f1 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -652,6 +652,8 @@ char* VM_PopulateDumpSharedSpace::dump_read_only_tables(AOTClassLocationConfig*& } void VM_PopulateDumpSharedSpace::doit() { + CDSConfig::set_is_at_aot_safepoint(true); + if (!CDSConfig::is_dumping_final_static_archive()) { guarantee(!CDSConfig::is_using_archive(), "We should not be using an archive when we dump"); } @@ -717,6 +719,8 @@ void VM_PopulateDumpSharedSpace::doit() { _map_info->set_serialized_data(serialized_data); _map_info->set_cloned_vtables(CppVtables::vtables_serialized_base()); _map_info->header()->set_class_location_config(cl_config); + + CDSConfig::set_is_at_aot_safepoint(false); } class CollectClassesForLinking : public KlassClosure { @@ -773,12 +777,9 @@ bool AOTMetaspace::may_be_eagerly_linked(InstanceKlass* ik) { return true; } -void AOTMetaspace::link_shared_classes(TRAPS) { - AOTClassLinker::initialize(); - AOTClassInitializer::init_test_class(CHECK); - +void AOTMetaspace::link_all_loaded_classes(JavaThread* current) { while (true) { - ResourceMark rm(THREAD); + ResourceMark rm(current); CollectClassesForLinking collect_classes; bool has_linked = false; const GrowableArray* mirrors = collect_classes.mirrors(); @@ -786,7 +787,7 @@ void AOTMetaspace::link_shared_classes(TRAPS) { OopHandle mirror = mirrors->at(i); InstanceKlass* ik = InstanceKlass::cast(java_lang_Class::as_Klass(mirror.resolve())); if (may_be_eagerly_linked(ik)) { - has_linked |= try_link_class(THREAD, ik); + has_linked |= try_link_class(current, ik); } } @@ -796,6 +797,13 @@ void AOTMetaspace::link_shared_classes(TRAPS) { // Class linking includes verification which may load more classes. // Keep scanning until we have linked no more classes. } +} + +void AOTMetaspace::link_shared_classes(TRAPS) { + AOTClassLinker::initialize(); + AOTClassInitializer::init_test_class(CHECK); + + link_all_loaded_classes(THREAD); // Eargerly resolve all string constants in constant pools { diff --git a/src/hotspot/share/cds/aotMetaspace.hpp b/src/hotspot/share/cds/aotMetaspace.hpp index 6c0ad37dbf7..1803199766d 100644 --- a/src/hotspot/share/cds/aotMetaspace.hpp +++ b/src/hotspot/share/cds/aotMetaspace.hpp @@ -135,6 +135,7 @@ public: } static bool try_link_class(JavaThread* current, InstanceKlass* ik); + static void link_all_loaded_classes(JavaThread* current); static void link_shared_classes(TRAPS) NOT_CDS_RETURN; static bool may_be_eagerly_linked(InstanceKlass* ik) NOT_CDS_RETURN_(false); diff --git a/src/hotspot/share/cds/archiveBuilder.cpp b/src/hotspot/share/cds/archiveBuilder.cpp index 42d575a012f..77f51443bb2 100644 --- a/src/hotspot/share/cds/archiveBuilder.cpp +++ b/src/hotspot/share/cds/archiveBuilder.cpp @@ -937,7 +937,7 @@ void ArchiveBuilder::make_klasses_shareable() { ADD_COUNT(num_enum_klasses); } - if (!ik->can_be_verified_at_dumptime()) { + if (CDSConfig::is_old_class_for_verifier(ik)) { ADD_COUNT(num_old_klasses); old = " old"; } diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index 90b802731f0..d3048b9ee7a 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -56,6 +56,7 @@ bool CDSConfig::_has_temp_aot_config_file = false; bool CDSConfig::_old_cds_flags_used = false; bool CDSConfig::_new_aot_flags_used = false; bool CDSConfig::_disable_heap_dumping = false; +bool CDSConfig::_is_at_aot_safepoint = false; const char* CDSConfig::_default_archive_path = nullptr; const char* CDSConfig::_input_static_archive_path = nullptr; @@ -922,6 +923,35 @@ bool CDSConfig::is_dumping_lambdas_in_legacy_mode() { return !is_dumping_method_handles(); } +bool CDSConfig::is_preserving_verification_constraints() { + // Verification dependencies are classes used in assignability checks by the + // bytecode verifier. In the following example, the verification dependencies + // for X are A and B. + // + // class X { + // A getA() { return new B(); } + // } + // + // With the AOT cache, we can ensure that all the verification dependencies + // (A and B in the above example) are unconditionally loaded during the bootstrap + // of the production run. This means that if a class was successfully verified + // in the assembly phase, all of the verifier's assignability checks will remain + // valid in the production run, so we don't need to verify aot-linked classes again. + + if (is_dumping_preimage_static_archive()) { // writing AOT config + return AOTClassLinking; + } else if (is_dumping_final_static_archive()) { // writing AOT cache + return is_dumping_aot_linked_classes(); + } else { + // For simplicity, we don't support this optimization with the old CDS workflow. + return false; + } +} + +bool CDSConfig::is_old_class_for_verifier(const InstanceKlass* ik) { + return ik->major_version() < 50 /*JAVA_6_VERSION*/; +} + #if INCLUDE_CDS_JAVA_HEAP bool CDSConfig::are_vm_options_incompatible_with_dumping_heap() { return check_options_incompatible_with_dumping_heap() != nullptr; diff --git a/src/hotspot/share/cds/cdsConfig.hpp b/src/hotspot/share/cds/cdsConfig.hpp index 1fd229ff34f..8361cf052db 100644 --- a/src/hotspot/share/cds/cdsConfig.hpp +++ b/src/hotspot/share/cds/cdsConfig.hpp @@ -30,6 +30,7 @@ #include "utilities/macros.hpp" class JavaThread; +class InstanceKlass; class CDSConfig : public AllStatic { #if INCLUDE_CDS @@ -43,6 +44,7 @@ class CDSConfig : public AllStatic { static bool _has_aot_linked_classes; static bool _is_single_command_training; static bool _has_temp_aot_config_file; + static bool _is_at_aot_safepoint; const static char* _default_archive_path; const static char* _input_static_archive_path; @@ -99,6 +101,9 @@ public: static const char* type_of_archive_being_written(); static void prepare_for_dumping(); + static bool is_at_aot_safepoint() { return CDS_ONLY(_is_at_aot_safepoint) NOT_CDS(false); } + static void set_is_at_aot_safepoint(bool value) { CDS_ONLY(_is_at_aot_safepoint = value); } + // --- Basic CDS features // archive(s) in general @@ -161,6 +166,10 @@ public: static bool is_using_aot_linked_classes() NOT_CDS_JAVA_HEAP_RETURN_(false); static void set_has_aot_linked_classes(bool has_aot_linked_classes) NOT_CDS_JAVA_HEAP_RETURN; + // Bytecode verification + static bool is_preserving_verification_constraints(); + static bool is_old_class_for_verifier(const InstanceKlass* ik); + // archive_path // Points to the classes.jsa in $JAVA_HOME (could be input or output) diff --git a/src/hotspot/share/cds/dumpTimeClassInfo.cpp b/src/hotspot/share/cds/dumpTimeClassInfo.cpp index 8af762dba4d..0f5773a2729 100644 --- a/src/hotspot/share/cds/dumpTimeClassInfo.cpp +++ b/src/hotspot/share/cds/dumpTimeClassInfo.cpp @@ -47,7 +47,7 @@ size_t DumpTimeClassInfo::runtime_info_bytesize() const { num_enum_klass_static_fields()); } -void DumpTimeClassInfo::add_verification_constraint(InstanceKlass* k, Symbol* name, +void DumpTimeClassInfo::add_verification_constraint(Symbol* name, Symbol* from_name, bool from_field_is_protected, bool from_is_array, bool from_is_object) { if (_verifier_constraints == nullptr) { _verifier_constraints = new (mtClass) GrowableArray(4, mtClass); @@ -73,9 +73,14 @@ void DumpTimeClassInfo::add_verification_constraint(InstanceKlass* k, Symbol* na if (log_is_enabled(Trace, aot, verification)) { ResourceMark rm; - log_trace(aot, verification)("add_verification_constraint: %s: %s must be subclass of %s [0x%x] array len %d flags len %d", - k->external_name(), from_name->as_klass_external_name(), - name->as_klass_external_name(), c, vc_array->length(), vcflags_array->length()); + if (from_name != nullptr) { + log_trace(aot, verification)("add verification constraint: %s: %s must be subclass of %s [0x%x]", + _klass->external_name(), from_name->as_klass_external_name(), + name->as_klass_external_name(), c); + } else { + log_trace(aot, verification)("added old verification constraint: %s: %s", _klass->external_name(), + name->as_klass_external_name()); + } } } diff --git a/src/hotspot/share/cds/dumpTimeClassInfo.hpp b/src/hotspot/share/cds/dumpTimeClassInfo.hpp index 0bc0f8bedda..c2f83b22337 100644 --- a/src/hotspot/share/cds/dumpTimeClassInfo.hpp +++ b/src/hotspot/share/cds/dumpTimeClassInfo.hpp @@ -88,7 +88,7 @@ class DumpTimeClassInfo: public CHeapObj { Symbol* _from_name; public: DTVerifierConstraint() : _name(nullptr), _from_name(nullptr) {} - DTVerifierConstraint(Symbol* n, Symbol* fn) : _name(n), _from_name(fn) { + DTVerifierConstraint(Symbol* n, Symbol* fn = nullptr) : _name(n), _from_name(fn) { Symbol::maybe_increment_refcount(_name); Symbol::maybe_increment_refcount(_from_name); } @@ -152,8 +152,9 @@ public: DumpTimeClassInfo& operator=(const DumpTimeClassInfo&) = delete; ~DumpTimeClassInfo(); - void add_verification_constraint(InstanceKlass* k, Symbol* name, - Symbol* from_name, bool from_field_is_protected, bool from_is_array, bool from_is_object); + // For old verifier: only name is saved; all other fields are null/false. + void add_verification_constraint(Symbol* name, + Symbol* from_name = nullptr, bool from_field_is_protected = false, bool from_is_array = false, bool from_is_object = false); void record_linking_constraint(Symbol* name, Handle loader1, Handle loader2); void add_enum_klass_static_field(int archived_heap_root_index); int enum_klass_static_field(int which_field); @@ -175,6 +176,14 @@ public: return array_length_or_zero(_verifier_constraint_flags); } + Symbol* verifier_constraint_name_at(int i) const { + return _verifier_constraints->at(i).name(); + } + + Symbol* verifier_constraint_from_name_at(int i) const { + return _verifier_constraints->at(i).from_name(); + } + int num_loader_constraints() const { return array_length_or_zero(_loader_constraints); } diff --git a/src/hotspot/share/cds/dynamicArchive.cpp b/src/hotspot/share/cds/dynamicArchive.cpp index d628a4e991f..58b354b9240 100644 --- a/src/hotspot/share/cds/dynamicArchive.cpp +++ b/src/hotspot/share/cds/dynamicArchive.cpp @@ -110,6 +110,12 @@ public: } void doit() { + CDSConfig::set_is_at_aot_safepoint(true); + doit_inner(); + CDSConfig::set_is_at_aot_safepoint(false); + } + + void doit_inner() { verify_universe("Before CDS dynamic dump"); DEBUG_ONLY(SystemDictionaryShared::NoClassLoadingMark nclm); diff --git a/src/hotspot/share/cds/lambdaProxyClassDictionary.cpp b/src/hotspot/share/cds/lambdaProxyClassDictionary.cpp index c8281ef497c..62b1b8c05f1 100644 --- a/src/hotspot/share/cds/lambdaProxyClassDictionary.cpp +++ b/src/hotspot/share/cds/lambdaProxyClassDictionary.cpp @@ -471,12 +471,12 @@ class LambdaProxyClassDictionary::CleanupDumpTimeLambdaProxyClassTable: StackObj // If the caller class and/or nest_host are excluded, the associated lambda proxy // must also be excluded. - bool always_exclude = SystemDictionaryShared::check_for_exclusion(caller_ik, nullptr) || - SystemDictionaryShared::check_for_exclusion(nest_host, nullptr); + bool always_exclude = SystemDictionaryShared::should_be_excluded(caller_ik) || + SystemDictionaryShared::should_be_excluded(nest_host); for (int i = info._proxy_klasses->length() - 1; i >= 0; i--) { InstanceKlass* ik = info._proxy_klasses->at(i); - if (always_exclude || SystemDictionaryShared::check_for_exclusion(ik, nullptr)) { + if (always_exclude || SystemDictionaryShared::should_be_excluded(ik)) { LambdaProxyClassDictionary::reset_registered_lambda_proxy_class(ik); info._proxy_klasses->remove_at(i); } diff --git a/src/hotspot/share/cds/runTimeClassInfo.cpp b/src/hotspot/share/cds/runTimeClassInfo.cpp index d93ef5e9c1d..832b0ce8932 100644 --- a/src/hotspot/share/cds/runTimeClassInfo.cpp +++ b/src/hotspot/share/cds/runTimeClassInfo.cpp @@ -40,12 +40,18 @@ void RunTimeClassInfo::init(DumpTimeClassInfo& info) { _num_verifier_constraints = info.num_verifier_constraints(); _num_loader_constraints = info.num_loader_constraints(); int i; + + if (CDSConfig::is_preserving_verification_constraints() && CDSConfig::is_dumping_final_static_archive()) { + // The production run doesn't need the verifier constraints, as we can guarantee that all classes checked by + // the verifier during AOT training/assembly phases cannot be replaced in the production run. + _num_verifier_constraints = 0; + } if (_num_verifier_constraints > 0) { RTVerifierConstraint* vf_constraints = verifier_constraints(); char* flags = verifier_constraint_flags(); for (i = 0; i < _num_verifier_constraints; i++) { - vf_constraints[i]._name = builder->any_to_offset_u4(info._verifier_constraints->at(i).name()); - vf_constraints[i]._from_name = builder->any_to_offset_u4(info._verifier_constraints->at(i).from_name()); + vf_constraints[i]._name = builder->any_to_offset_u4(info._verifier_constraints->at(i).name()); + vf_constraints[i]._from_name = builder->any_or_null_to_offset_u4(info._verifier_constraints->at(i).from_name()); } for (i = 0; i < _num_verifier_constraints; i++) { flags[i] = info._verifier_constraint_flags->at(i); diff --git a/src/hotspot/share/cds/runTimeClassInfo.hpp b/src/hotspot/share/cds/runTimeClassInfo.hpp index 29670f5ec51..371924f9065 100644 --- a/src/hotspot/share/cds/runTimeClassInfo.hpp +++ b/src/hotspot/share/cds/runTimeClassInfo.hpp @@ -59,7 +59,9 @@ class RunTimeClassInfo { u4 _name; u4 _from_name; Symbol* name() { return ArchiveUtils::offset_to_archived_address(_name); } - Symbol* from_name() { return ArchiveUtils::offset_to_archived_address(_from_name); } + Symbol* from_name() { + return (_from_name == 0) ? nullptr : ArchiveUtils::offset_to_archived_address(_from_name); + } }; struct RTLoaderConstraint { diff --git a/src/hotspot/share/classfile/systemDictionaryShared.cpp b/src/hotspot/share/classfile/systemDictionaryShared.cpp index 45a5dc2328c..eda823704ca 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.cpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.cpp @@ -204,28 +204,156 @@ DumpTimeClassInfo* SystemDictionaryShared::get_info_locked(InstanceKlass* k) { return info; } -bool SystemDictionaryShared::check_for_exclusion(InstanceKlass* k, DumpTimeClassInfo* info) { - if (CDSConfig::is_dumping_dynamic_archive() && AOTMetaspace::in_aot_cache(k)) { - // We have reached a super type that's already in the base archive. Treat it - // as "not excluded". - return false; - } - - if (info == nullptr) { - info = _dumptime_table->get(k); - assert(info != nullptr, "supertypes of any classes in _dumptime_table must either be shared, or must also be in _dumptime_table"); - } +bool SystemDictionaryShared::should_be_excluded_impl(InstanceKlass* k, DumpTimeClassInfo* info) { + assert_lock_strong(DumpTimeTable_lock); if (!info->has_checked_exclusion()) { - if (check_for_exclusion_impl(k)) { - info->set_excluded(); - } - info->set_has_checked_exclusion(); + check_exclusion_for_self_and_dependencies(k); + assert(info->has_checked_exclusion(), "must be"); } return info->is_excluded(); } +// returns bool and takes a single parameter of Symbol* +// The return value indicates whether we want to keep on iterating or not. +template +void SystemDictionaryShared::iterate_verification_constraint_names(InstanceKlass* k, DumpTimeClassInfo* info, Function func) { + int n = info->num_verifier_constraints(); + bool cont; // continue iterating? + for (int i = 0; i < n; i++) { + cont = func(info->verifier_constraint_name_at(i)); + if (!cont) { + return; // early termination + } + Symbol* from_name = info->verifier_constraint_from_name_at(i); + if (from_name != nullptr) { + cont = func(from_name); + if (!cont) { + return; // early termination + } + } + } +} + +// This is a table of classes that need to be checked for exclusion. +class SystemDictionaryShared::ExclusionCheckCandidates + : public HashTable { + void add_candidate(InstanceKlass* k) { + if (contains(k)) { + return; + } + if (CDSConfig::is_dumping_dynamic_archive() && AOTMetaspace::in_aot_cache(k)) { + return; + } + + DumpTimeClassInfo* info = SystemDictionaryShared::get_info_locked(k); + if (info->has_checked_exclusion()) { + // We have check exclusion of k and all of its dependencies, so there's no need to check again. + return; + } + + put(k, info); + + if (!k->is_loaded()) { + // super types are not yet initialized for k. + return; + } + + InstanceKlass* super = k->java_super(); + if (super != nullptr) { + add_candidate(super); + } + + Array* interfaces = k->local_interfaces(); + int len = interfaces->length(); + for (int i = 0; i < len; i++) { + add_candidate(interfaces->at(i)); + } + + InstanceKlass* nest_host = k->nest_host_or_null(); + if (nest_host != nullptr && nest_host != k) { + add_candidate(nest_host); + } + + if (CDSConfig::is_preserving_verification_constraints()) { + SystemDictionaryShared::iterate_verification_constraint_names(k, info, [&] (Symbol* constraint_class_name) { + Klass* constraint_bottom_class = find_verification_constraint_bottom_class(k, constraint_class_name); + if (constraint_bottom_class != nullptr && constraint_bottom_class->is_instance_klass()) { + add_candidate(InstanceKlass::cast(constraint_bottom_class)); + } + return true; // Keep iterating. + }); + } + } + +public: + ExclusionCheckCandidates(InstanceKlass* k) { + add_candidate(k); + } +}; + +// A class X is excluded if check_self_exclusion() returns true for X or any of +// X's "exclusion dependency" classes, which include: +// - ik's super types +// - ik's nest host (if any) +// +// plus, if CDSConfig::is_preserving_verification_constraints()==true: +// - ik's verification constraints. These are the classes used in assignability checks +// when verifying ik's bytecodes. +// +// This method ensure that exclusion check is performed on X and all of its exclusion dependencies. +void SystemDictionaryShared::check_exclusion_for_self_and_dependencies(InstanceKlass* ik) { + assert_lock_strong(DumpTimeTable_lock); + ResourceMark rm; + + // This will recursively find ik and all of its exclusion dependencies that have not yet been checked. + ExclusionCheckCandidates candidates(ik); + + // (1) Check each class to see if it should be excluded due to its own problems + candidates.iterate_all([&] (InstanceKlass* k, DumpTimeClassInfo* info) { + if (check_self_exclusion(k)) { + info->set_excluded(); + } + }); + + // (2) Check each class to see if it should be excluded because of problems in a depeendency class + while (true) { + bool found_new_exclusion = false; + + candidates.iterate_all([&] (InstanceKlass* k, DumpTimeClassInfo* info) { + if (!info->is_excluded() && check_dependencies_exclusion(k, info)) { + info->set_excluded(); + found_new_exclusion = true; + } + }); + + // Algorithm notes: + // + // The dependencies form a directed graph, possibly cyclic. Class X is excluded + // if it has at least one directed path that reaches class Y, where + // check_self_exclusion(Y) returns true. + // + // Because of the possibility of cycles in the graph, we cannot use simple + // recursion. Otherwise we will either never terminate, or will miss some paths. + // + // Hence, we keep doing a linear scan of the candidates until we stop finding + // new exclusions. + // + // In the worst case, we find one exclusion per iteration of the while loop, + // so the while loop gets executed O(N^2) times. However, in reality we have + // very few exclusions, so in most cases the while loop executes only once, and we + // walk each edge in the dependencies graph exactly once. + if (!found_new_exclusion) { + break; + } + } + candidates.iterate_all([&] (InstanceKlass* k, DumpTimeClassInfo* info) { + // All candidates have been fully checked, so we don't need to check them again. + info->set_has_checked_exclusion(); + }); +} + // Returns true so the caller can do: return warn_excluded("....."); bool SystemDictionaryShared::warn_excluded(InstanceKlass* k, const char* reason) { ResourceMark rm; @@ -248,7 +376,8 @@ bool SystemDictionaryShared::is_early_klass(InstanceKlass* ik) { return (info != nullptr) ? info->is_early_klass() : false; } -bool SystemDictionaryShared::check_for_exclusion_impl(InstanceKlass* k) { +bool SystemDictionaryShared::check_self_exclusion(InstanceKlass* k) { + assert_lock_strong(DumpTimeTable_lock); if (CDSConfig::is_dumping_final_static_archive() && k->defined_by_other_loaders() && k->in_aot_cache()) { return false; // Do not exclude: unregistered classes are passed from preimage to final image. @@ -301,9 +430,8 @@ bool SystemDictionaryShared::check_for_exclusion_impl(InstanceKlass* k) { return warn_excluded(k, "Failed verification"); } else if (CDSConfig::is_dumping_aot_linked_classes()) { // Most loaded classes should have been speculatively linked by AOTMetaspace::link_class_for_cds(). - // However, we do not speculatively link old classes, as they are not recorded by - // SystemDictionaryShared::record_linking_constraint(). As a result, such an unlinked - // class may fail to verify in AOTLinkedClassBulkLoader::init_required_classes_for_loader(), + // Old classes may not be linked if CDSConfig::is_preserving_verification_constraints()==false. + // An unlinked class may fail to verify in AOTLinkedClassBulkLoader::init_required_classes_for_loader(), // causing the JVM to fail at bootstrap. return warn_excluded(k, "Unlinked class not supported by AOTClassLinking"); } else if (CDSConfig::is_dumping_preimage_static_archive()) { @@ -329,10 +457,13 @@ bool SystemDictionaryShared::check_for_exclusion_impl(InstanceKlass* k) { return true; } - InstanceKlass* super = k->super(); - if (super != nullptr && check_for_exclusion(super, nullptr)) { - ResourceMark rm; - aot_log_warning(aot)("Skipping %s: super class %s is excluded", k->name()->as_C_string(), super->name()->as_C_string()); + return false; +} + +// Returns true if DumpTimeClassInfo::is_excluded() is true for at least one of k's exclusion dependencies. +bool SystemDictionaryShared::check_dependencies_exclusion(InstanceKlass* k, DumpTimeClassInfo* info) { + InstanceKlass* super = k->java_super(); + if (super != nullptr && is_dependency_excluded(k, super, "super")) { return true; } @@ -340,21 +471,87 @@ bool SystemDictionaryShared::check_for_exclusion_impl(InstanceKlass* k) { int len = interfaces->length(); for (int i = 0; i < len; i++) { InstanceKlass* intf = interfaces->at(i); - if (check_for_exclusion(intf, nullptr)) { - ResourceMark rm; - aot_log_warning(aot)("Skipping %s: interface %s is excluded", k->name()->as_C_string(), intf->name()->as_C_string()); + if (is_dependency_excluded(k, intf, "interface")) { return true; } } InstanceKlass* nest_host = k->nest_host_or_null(); - if (nest_host != nullptr && nest_host != k && check_for_exclusion(nest_host, nullptr)) { - ResourceMark rm; - aot_log_warning(aot)("Skipping %s: nest_host class %s is excluded", k->name()->as_C_string(), nest_host->name()->as_C_string()); + if (nest_host != nullptr && nest_host != k && is_dependency_excluded(k, nest_host, "nest host class")) { return true; } - return false; // false == k should NOT be excluded + if (CDSConfig::is_preserving_verification_constraints()) { + bool excluded = false; + + iterate_verification_constraint_names(k, info, [&] (Symbol* constraint_class_name) { + if (check_verification_constraint_exclusion(k, constraint_class_name)) { + // If one of the verification constraint class has been excluded, the assignability checks + // by the verifier may no longer be valid in the production run. For safety, exclude this class. + excluded = true; + return false; // terminate iteration; k will be excluded + } else { + return true; // keep iterating + } + }); + + if (excluded) { + // At least one verification constraint class has been excluded + return true; + } + } + + return false; +} + +bool SystemDictionaryShared::is_dependency_excluded(InstanceKlass* k, InstanceKlass* dependency, const char* type) { + if (CDSConfig::is_dumping_dynamic_archive() && AOTMetaspace::in_aot_cache(dependency)) { + return false; + } + DumpTimeClassInfo* dependency_info = get_info_locked(dependency); + if (dependency_info->is_excluded()) { + ResourceMark rm; + aot_log_warning(aot)("Skipping %s: %s %s is excluded", k->name()->as_C_string(), type, dependency->name()->as_C_string()); + return true; + } + return false; +} + +bool SystemDictionaryShared::check_verification_constraint_exclusion(InstanceKlass* k, Symbol* constraint_class_name) { + Klass* constraint_bottom_class = find_verification_constraint_bottom_class(k, constraint_class_name); + if (constraint_bottom_class == nullptr) { + // We don't have a bottom class (constraint_class_name is a type array), or constraint_class_name + // has not been loaded. The latter case happens when the new verifier was checking + // if constraint_class_name is assignable to an interface, and found the answer without resolving + // constraint_class_name. + // + // Since this class is not even loaded, it surely cannot be excluded. + return false; + } else if (constraint_bottom_class->is_instance_klass()) { + if (is_dependency_excluded(k, InstanceKlass::cast(constraint_bottom_class), "verification constraint")) { + return true; + } + } else { + assert(constraint_bottom_class->is_typeArray_klass(), "must be"); + } + + return false; +} + +Klass* SystemDictionaryShared::find_verification_constraint_bottom_class(InstanceKlass* k, Symbol* constraint_class_name) { + Thread* current = Thread::current(); + Handle loader(current, k->class_loader()); + Klass* constraint_class = SystemDictionary::find_instance_or_array_klass(current, constraint_class_name, loader); + if (constraint_class == nullptr) { + return nullptr; + } + + if (constraint_class->is_objArray_klass()) { + constraint_class = ObjArrayKlass::cast(constraint_class)->bottom_klass(); + } + + precond(constraint_class->is_typeArray_klass() || constraint_class->is_instance_klass()); + return constraint_class; } bool SystemDictionaryShared::is_builtin_loader(ClassLoaderData* loader_data) { @@ -556,7 +753,7 @@ void SystemDictionaryShared::handle_class_unloading(InstanceKlass* klass) { void SystemDictionaryShared::init_dumptime_info_from_preimage(InstanceKlass* k) { init_dumptime_info(k); - copy_verification_constraints_from_preimage(k); + copy_verification_info_from_preimage(k); copy_linking_constraints_from_preimage(k); if (SystemDictionary::is_platform_class_loader(k->class_loader())) { @@ -651,16 +848,21 @@ public: // Returns true if the class should be excluded. This can be called by // AOTConstantPoolResolver before or after we enter the CDS safepoint. // When called before the safepoint, we need to link the class so that -// it can be checked by check_for_exclusion(). +// it can be checked by should_be_excluded_impl(). bool SystemDictionaryShared::should_be_excluded(Klass* k) { assert(CDSConfig::is_dumping_archive(), "sanity"); assert(CDSConfig::current_thread_is_vm_or_dumper(), "sanity"); - if (k->is_objArray_klass()) { - return should_be_excluded(ObjArrayKlass::cast(k)->bottom_klass()); + if (CDSConfig::is_dumping_dynamic_archive() && AOTMetaspace::in_aot_cache(k)) { + // We have reached a super type that's already in the base archive. Treat it + // as "not excluded". + return false; } - if (!k->is_instance_klass()) { + if (k->is_objArray_klass()) { + return should_be_excluded(ObjArrayKlass::cast(k)->bottom_klass()); + } else if (!k->is_instance_klass()) { + assert(k->is_typeArray_klass(), "must be"); return false; } else { InstanceKlass* ik = InstanceKlass::cast(k); @@ -672,7 +874,7 @@ bool SystemDictionaryShared::should_be_excluded(Klass* k) { if (!SafepointSynchronize::is_at_safepoint()) { if (!ik->is_linked()) { - // check_for_exclusion() below doesn't link unlinked classes. We come + // should_be_excluded_impl() below doesn't link unlinked classes. We come // here only when we are trying to aot-link constant pool entries, so // we'd better link the class. JavaThread* THREAD = JavaThread::current(); @@ -681,6 +883,10 @@ bool SystemDictionaryShared::should_be_excluded(Klass* k) { CLEAR_PENDING_EXCEPTION; return true; // linking failed -- let's exclude it } + + // Also link any classes that were loaded for the verification of ik or its supertypes. + // Otherwise we might miss the verification constraints of those classes. + AOTMetaspace::link_all_loaded_classes(THREAD); } MutexLocker ml(DumpTimeTable_lock, Mutex::_no_safepoint_check_flag); @@ -688,8 +894,17 @@ bool SystemDictionaryShared::should_be_excluded(Klass* k) { if (p->is_excluded()) { return true; } - return check_for_exclusion(ik, p); + return should_be_excluded_impl(ik, p); } else { + // When called within the CDS safepoint, the correctness of this function + // relies on the call to AOTMetaspace::link_all_loaded_classes() + // that happened right before we enter the CDS safepoint. + // + // Do not call this function in other types of safepoints. For example, if this + // is called in a GC safepoint, a klass may be improperly excluded because some + // of its verification constraints have not yet been linked. + assert(CDSConfig::is_at_aot_safepoint(), "Do not call this function in any other safepoint"); + // No need to check for is_linked() as all eligible classes should have // already been linked in AOTMetaspace::link_class_for_cds(). // Can't take the lock as we are in safepoint. @@ -697,12 +912,13 @@ bool SystemDictionaryShared::should_be_excluded(Klass* k) { if (p->is_excluded()) { return true; } - return check_for_exclusion(ik, p); + return should_be_excluded_impl(ik, p); } } } void SystemDictionaryShared::finish_exclusion_checks() { + assert_at_safepoint(); if (CDSConfig::is_dumping_dynamic_archive() || CDSConfig::is_dumping_preimage_static_archive()) { // Do this first -- if a base class is excluded due to duplication, // all of its subclasses will also be excluded. @@ -713,7 +929,7 @@ void SystemDictionaryShared::finish_exclusion_checks() { } _dumptime_table->iterate_all_live_classes([&] (InstanceKlass* k, DumpTimeClassInfo& info) { - SystemDictionaryShared::check_for_exclusion(k, &info); + SystemDictionaryShared::should_be_excluded_impl(k, &info); }); _dumptime_table->update_counts(); @@ -793,7 +1009,7 @@ void SystemDictionaryShared::add_verification_constraint(InstanceKlass* k, Symbo bool* skip_assignability_check) { assert(CDSConfig::is_dumping_archive(), "sanity"); DumpTimeClassInfo* info = get_info(k); - info->add_verification_constraint(k, name, from_name, from_field_is_protected, + info->add_verification_constraint(name, from_name, from_field_is_protected, from_is_array, from_is_object); if (CDSConfig::is_dumping_classic_static_archive() && !is_builtin(k)) { @@ -818,6 +1034,15 @@ void SystemDictionaryShared::add_verification_constraint(InstanceKlass* k, Symbo } } +// When the old verifier is verifying the class at dump time, it tries to resolve a +// class with the given . For the verification result to be valid at run time, we must +// ensure that resolves to the exact same Klass as in dump time. +void SystemDictionaryShared::add_old_verification_constraint(Thread* current, InstanceKlass* ik, Symbol* name) { + precond(CDSConfig::is_preserving_verification_constraints()); + DumpTimeClassInfo* info = get_info(ik); + info->add_verification_constraint(name); +} + void SystemDictionaryShared::add_enum_klass_static_field(InstanceKlass* ik, int root_index) { assert(CDSConfig::is_dumping_heap(), "sanity"); DumpTimeClassInfo* info = get_info_locked(ik); @@ -836,6 +1061,13 @@ void SystemDictionaryShared::check_verification_constraints(InstanceKlass* klass Symbol* name = vc->name(); Symbol* from_name = vc->from_name(); + if (from_name == nullptr) { + // This is for old verifier. No need to check, as we can guarantee that all classes checked by + // the old verifier during AOT training phase cannot be replaced in the asembly phase. + precond(CDSConfig::is_dumping_final_static_archive()); + continue; + } + if (log_is_enabled(Trace, aot, verification)) { ResourceMark rm(THREAD); log_trace(aot, verification)("check_verification_constraint: %s: %s must be subclass of %s [0x%x]", @@ -860,7 +1092,7 @@ void SystemDictionaryShared::check_verification_constraints(InstanceKlass* klass } } -void SystemDictionaryShared::copy_verification_constraints_from_preimage(InstanceKlass* klass) { +void SystemDictionaryShared::copy_verification_info_from_preimage(InstanceKlass* klass) { assert(CDSConfig::is_using_archive(), "called at run time with CDS enabled only"); DumpTimeClassInfo* dt_info = get_info(klass); RunTimeClassInfo* rt_info = RunTimeClassInfo::get_for(klass); // from preimage @@ -872,7 +1104,7 @@ void SystemDictionaryShared::copy_verification_constraints_from_preimage(Instanc Symbol* name = vc->name(); Symbol* from_name = vc->from_name(); - dt_info->add_verification_constraint(klass, name, from_name, + dt_info->add_verification_constraint(name, from_name, rt_info->from_field_is_protected(i), rt_info->from_is_array(i), rt_info->from_is_object(i)); } } diff --git a/src/hotspot/share/classfile/systemDictionaryShared.hpp b/src/hotspot/share/classfile/systemDictionaryShared.hpp index 30b38a5aa59..baad020cb61 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.hpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.hpp @@ -146,7 +146,7 @@ class SystemDictionaryShared: public SystemDictionary { }; private: - + class ExclusionCheckCandidates; static DumpTimeSharedClassTable* _dumptime_table; static ArchiveInfo _static_archive; @@ -175,14 +175,27 @@ private: static void write_dictionary(RunTimeSharedDictionary* dictionary, bool is_builtin); static bool is_jfr_event_class(InstanceKlass *k); - static bool check_for_exclusion_impl(InstanceKlass* k); + static bool should_be_excluded_impl(InstanceKlass* k, DumpTimeClassInfo* info); + + // exclusion checks + static void check_exclusion_for_self_and_dependencies(InstanceKlass *k); + static bool check_self_exclusion(InstanceKlass* k); + static bool check_dependencies_exclusion(InstanceKlass* k, DumpTimeClassInfo* info); + static bool check_verification_constraint_exclusion(InstanceKlass* k, Symbol* constraint_class_name); + static bool is_dependency_excluded(InstanceKlass* k, InstanceKlass* dependency, const char* type); + static bool is_excluded_verification_constraint(InstanceKlass* k, Symbol* constraint_class_name); + static Klass* find_verification_constraint_bottom_class(InstanceKlass* k, Symbol* constraint_class_name); + static void remove_dumptime_info(InstanceKlass* k) NOT_CDS_RETURN; static bool has_been_redefined(InstanceKlass* k); DEBUG_ONLY(static bool _class_loading_may_happen;) - static void copy_verification_constraints_from_preimage(InstanceKlass* klass); + static void copy_verification_info_from_preimage(InstanceKlass* klass); static void copy_linking_constraints_from_preimage(InstanceKlass* klass); + template + static void iterate_verification_constraint_names(InstanceKlass* k, DumpTimeClassInfo* info, Function func); + public: static bool is_early_klass(InstanceKlass* k); // Was k loaded while JvmtiExport::is_early_phase()==true static bool has_archived_enum_objs(InstanceKlass* ik); @@ -239,6 +252,7 @@ public: Symbol* from_name, bool from_field_is_protected, bool from_is_array, bool from_is_object, bool* skip_assignability_check); + static void add_old_verification_constraint(Thread* current, InstanceKlass* k, Symbol* name); static void check_verification_constraints(InstanceKlass* klass, TRAPS) NOT_CDS_RETURN; static void add_enum_klass_static_field(InstanceKlass* ik, int root_index); @@ -258,7 +272,6 @@ public: static DumpTimeSharedClassTable* dumptime_table() { return _dumptime_table; } static bool should_be_excluded(Klass* k); - static bool check_for_exclusion(InstanceKlass* k, DumpTimeClassInfo* info); static void validate_before_archiving(InstanceKlass* k); static bool is_excluded_class(InstanceKlass* k); static void set_excluded(InstanceKlass* k); diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index 1afc59d8da1..e0ebb92c7ae 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -2839,18 +2839,20 @@ void InstanceKlass::restore_unshareable_info(ClassLoaderData* loader_data, Handl DEBUG_ONLY(FieldInfoStream::validate_search_table(_constants, _fieldinfo_stream, _fieldinfo_search_table)); } -// Check if a class or any of its supertypes has a version older than 50. -// CDS will not perform verification of old classes during dump time because -// without changing the old verifier, the verification constraint cannot be -// retrieved during dump time. -// Verification of archived old classes will be performed during run time. bool InstanceKlass::can_be_verified_at_dumptime() const { if (AOTMetaspace::in_aot_cache(this)) { // This is a class that was dumped into the base archive, so we know // it was verified at dump time. return true; } - if (major_version() < 50 /*JAVA_6_VERSION*/) { + + if (CDSConfig::is_preserving_verification_constraints()) { + return true; + } + + if (CDSConfig::is_old_class_for_verifier(this)) { + // The old verifier does not save verification constraints, so at run time + // SystemDictionaryShared::check_verification_constraints() will not work for this class. return false; } if (super() != nullptr && !super()->can_be_verified_at_dumptime()) { diff --git a/src/hotspot/share/oops/methodData.cpp b/src/hotspot/share/oops/methodData.cpp index 4c027e0839a..0463d8d9a81 100644 --- a/src/hotspot/share/oops/methodData.cpp +++ b/src/hotspot/share/oops/methodData.cpp @@ -323,9 +323,8 @@ void VirtualCallTypeData::post_initialize(BytecodeStream* stream, MethodData* md static bool is_excluded(Klass* k) { #if INCLUDE_CDS - if (SafepointSynchronize::is_at_safepoint() && - CDSConfig::is_dumping_archive() && - CDSConfig::current_thread_is_vm_or_dumper()) { + if (CDSConfig::is_at_aot_safepoint()) { + // Check for CDS exclusion only at CDS safe point. if (k->is_instance_klass() && !InstanceKlass::cast(k)->is_loaded()) { log_debug(aot, training)("Purged %s from MDO: unloaded class", k->name()->as_C_string()); return true; diff --git a/src/hotspot/share/oops/trainingData.cpp b/src/hotspot/share/oops/trainingData.cpp index 845dc20c0d0..8f906ae3d37 100644 --- a/src/hotspot/share/oops/trainingData.cpp +++ b/src/hotspot/share/oops/trainingData.cpp @@ -554,7 +554,11 @@ void KlassTrainingData::cleanup(Visitor& visitor) { } visitor.visit(this); if (has_holder()) { - bool is_excluded = !holder()->is_loaded() || SystemDictionaryShared::check_for_exclusion(holder(), nullptr); + bool is_excluded = !holder()->is_loaded(); + if (CDSConfig::is_at_aot_safepoint()) { + // Check for AOT exclusion only at AOT safe point. + is_excluded |= SystemDictionaryShared::should_be_excluded(holder()); + } if (is_excluded) { ResourceMark rm; log_debug(aot, training)("Cleanup KTD %s", name()->as_klass_external_name()); @@ -573,7 +577,8 @@ void MethodTrainingData::cleanup(Visitor& visitor) { } visitor.visit(this); if (has_holder()) { - if (SystemDictionaryShared::check_for_exclusion(holder()->method_holder(), nullptr)) { + if (CDSConfig::is_at_aot_safepoint() && SystemDictionaryShared::should_be_excluded(holder()->method_holder())) { + // Check for AOT exclusion only at AOT safe point. log_debug(aot, training)("Cleanup MTD %s::%s", name()->as_klass_external_name(), signature()->as_utf8()); if (_final_profile != nullptr && _final_profile->method() != _holder) { log_warning(aot, training)("Stale MDO for %s::%s", name()->as_klass_external_name(), signature()->as_utf8()); diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index 99c8a56c727..0651c173e7b 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -850,6 +850,13 @@ JVM_ENTRY(jclass, JVM_FindClassFromClass(JNIEnv *env, const char *name, log_debug(class, resolve)("%s %s (verification)", from_name, to); } +#if INCLUDE_CDS + if (CDSConfig::is_preserving_verification_constraints() && from_class->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(from_class); + SystemDictionaryShared::add_old_verification_constraint(THREAD, ik, h_name); + } +#endif + return result; JVM_END diff --git a/src/hotspot/share/runtime/mutexLocker.cpp b/src/hotspot/share/runtime/mutexLocker.cpp index 0c604205939..e0eafbc416b 100644 --- a/src/hotspot/share/runtime/mutexLocker.cpp +++ b/src/hotspot/share/runtime/mutexLocker.cpp @@ -303,11 +303,11 @@ void mutex_init() { #endif MUTEX_DEFN(DumpTimeTable_lock , PaddedMutex , nosafepoint); MUTEX_DEFN(CDSLambda_lock , PaddedMutex , nosafepoint); - MUTEX_DEFN(DumpRegion_lock , PaddedMutex , nosafepoint); + MUTEX_DEFL(DumpRegion_lock , PaddedMutex , DumpTimeTable_lock); MUTEX_DEFN(ClassListFile_lock , PaddedMutex , nosafepoint); MUTEX_DEFN(UnregisteredClassesTable_lock , PaddedMutex , nosafepoint-1); MUTEX_DEFN(LambdaFormInvokers_lock , PaddedMutex , safepoint); - MUTEX_DEFN(ScratchObjects_lock , PaddedMutex , nosafepoint-1); // Holds DumpTimeTable_lock + MUTEX_DEFL(ScratchObjects_lock , PaddedMutex , DumpTimeTable_lock); MUTEX_DEFN(FinalImageRecipes_lock , PaddedMutex , nosafepoint); #endif // INCLUDE_CDS MUTEX_DEFN(Bootclasspath_lock , PaddedMutex , nosafepoint); diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index eeb5110b077..3af6548fe33 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -526,6 +526,7 @@ hotspot_aot_classlinking = \ -runtime/cds/appcds/cacheObject/ArchivedIntegerCacheTest.java \ -runtime/cds/appcds/cacheObject/ArchivedModuleCompareTest.java \ -runtime/cds/appcds/CDSandJFR.java \ + -runtime/cds/appcds/LambdaContainsOldInf.java \ -runtime/cds/appcds/customLoader/CustomClassListDump.java \ -runtime/cds/appcds/customLoader/HelloCustom_JFR.java \ -runtime/cds/appcds/customLoader/OldClassAndInf.java \ @@ -533,14 +534,17 @@ hotspot_aot_classlinking = \ -runtime/cds/appcds/customLoader/ParallelTestSingleFP.java \ -runtime/cds/appcds/customLoader/SameNameInTwoLoadersTest.java \ -runtime/cds/appcds/DumpClassListWithLF.java \ - -runtime/cds/appcds/dynamicArchive/ModulePath.java \ + -runtime/cds/appcds/dynamicArchive/LambdaContainsOldInf.java \ -runtime/cds/appcds/dynamicArchive/LambdaCustomLoader.java \ -runtime/cds/appcds/dynamicArchive/LambdaForOldInfInBaseArchive.java \ -runtime/cds/appcds/dynamicArchive/LambdaInBaseArchive.java \ -runtime/cds/appcds/dynamicArchive/LambdasInTwoArchives.java \ + -runtime/cds/appcds/dynamicArchive/ModulePath.java \ + -runtime/cds/appcds/dynamicArchive/NestHostOldInf.java \ -runtime/cds/appcds/dynamicArchive/OldClassAndInf.java \ -runtime/cds/appcds/dynamicArchive/OldClassInBaseArchive.java \ -runtime/cds/appcds/dynamicArchive/OldClassVerifierTrouble.java \ + -runtime/cds/appcds/dynamicArchive/RedefineCallerClassTest.java \ -runtime/cds/appcds/HelloExtTest.java \ -runtime/cds/appcds/javaldr/ExceptionDuringDumpAtObjectsInitPhase.java \ -runtime/cds/appcds/javaldr/GCDuringDump.java \ diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ExcludedClasses.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ExcludedClasses.java index f50a2d1f905..9a9524eb2f1 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ExcludedClasses.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ExcludedClasses.java @@ -99,7 +99,6 @@ public class ExcludedClasses { if (runMode == RunMode.ASSEMBLY) { out.shouldNotMatch("aot,resolve.*archived field.*TestApp.Foo => TestApp.Foo.ShouldBeExcluded.f:I"); } else if (runMode == RunMode.PRODUCTION) { - out.shouldContain("check_verification_constraint: TestApp$Foo$Taz: TestApp$Foo$ShouldBeExcludedChild must be subclass of TestApp$Foo$ShouldBeExcluded"); out.shouldContain("jdk.jfr.Event source: jrt:/jdk.jfr"); out.shouldMatch("TestApp[$]Foo[$]ShouldBeExcluded source: .*/app.jar"); out.shouldMatch("TestApp[$]Foo[$]ShouldBeExcludedChild source: .*/app.jar"); @@ -259,14 +258,9 @@ class TestApp { static class Taz { static ShouldBeExcluded m() { - // When verifying this method, we need to check the constraint that - // ShouldBeExcluded must be a supertype of ShouldBeExcludedChild. This information - // is checked by SystemDictionaryShared::check_verification_constraints() when the Taz - // class is linked during the production run. - // - // Because ShouldBeExcluded is excluded from the AOT archive, it must be loaded - // dynamically from app.jar inside SystemDictionaryShared::check_verification_constraints(). - // This must happen after the app class loader has been fully restored from the AOT cache. + // Taz should be excluded from the AOT cache because it has a verification constraint that + // "ShouldBeExcludedChild must be a subtype of ShouldBeExcluded", but ShouldBeExcluded is + // excluded from the AOT cache. return new ShouldBeExcludedChild(); } static void hotSpot4() { diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldA.jasm b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldA.jasm new file mode 100644 index 00000000000..e0362eb0649 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldA.jasm @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025, 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. + * + */ + +super public class OldA + version 49:0 +{ + + +public Method "":"()V" + stack 1 locals 1 +{ + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; +} + +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldClassSupport.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldClassSupport.java new file mode 100644 index 00000000000..42161b469bf --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldClassSupport.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2025, 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 Store old classes linked state in AOT cache as long as their verification constraints are not excluded. + * @bug 8317269 + * @requires vm.cds.supports.aot.class.linking + * @library /test/jdk/lib/testlibrary /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes + * @build OldClass OldA OldClassWithVerifierConstraints OldClassWithExcludedVerifierConstraints + * @build OldClassSupport + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar + * AppUsesOldClass MyIntf OldClass OldA NewB MyEvent MyEvent2 + * OldClassWithVerifierConstraints + * OldClassWithExcludedVerifierConstraints + * NewClassWithExcludedVerifierConstraints + * @run driver OldClassSupport + */ + +import jdk.jfr.Event; +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; + +public class OldClassSupport { + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + static final String mainClass = "AppUsesOldClass"; + + public static void main(String[] args) throws Exception { + Tester tester = new Tester(); + tester.run(new String[] {"AOT", "--two-step-training"} ); + } + + static class Tester extends CDSAppTester { + public Tester() { + super(mainClass); + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] vmArgs(RunMode runMode) { + return new String[] { + "-Xlog:aot+class=debug", + "-Xlog:aot+resolve=trace", + }; + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] {"-Xlog:cds+class=debug", mainClass}; + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) { + Class[] included = { + OldClass.class, + OldA.class, + NewB.class, + OldClassWithVerifierConstraints.class, + }; + + Class[] excluded = { + OldClassWithExcludedVerifierConstraints.class, + NewClassWithExcludedVerifierConstraints.class, + }; + + + if (runMode == RunMode.TRAINING) { + shouldInclude(out, false, included); + shouldNotInclude(out, excluded); + shouldSkip(out, excluded); + } else if (runMode == RunMode.ASSEMBLY) { + shouldInclude(out, true, included); + shouldNotInclude(out, excluded); + } + } + } + + static void shouldInclude(OutputAnalyzer out, boolean linked, Class[] classes) { + for (Class c : classes) { + out.shouldMatch("aot,class.* = 0x.* app *" + c.getName() + (linked ? " .*aot-linked" : "")); + } + } + + static void shouldNotInclude(OutputAnalyzer out, Class[] classes) { + for (Class c : classes) { + out.shouldNotMatch("aot,class.* = 0x.* app *" + c.getName()); + } + } + + static void shouldSkip(OutputAnalyzer out, Class[] classes) { + for (Class c : classes) { + out.shouldMatch("Skipping " + c.getName() + ": verification constraint .* is excluded"); + } + } +} + +class AppUsesOldClass { + public static void main(String args[]) { + System.out.println("Old Class Instance: " + new OldClass()); + + System.out.println(get_OldA_from_NewB()); + System.out.println(OldClassWithVerifierConstraints.get_OldA_from_NewB()); + System.out.println(OldClassWithExcludedVerifierConstraints.get_Event_from_MyEvent()); + System.out.println(NewClassWithExcludedVerifierConstraints.get_MyEvent_from_MyEvent2()); + System.out.println(new MyEvent()); + + // OldClassWithExcludedVerifierConstraints should still be excluded even it has been used + // in a lambda expression during the training run. + run((OldClassWithExcludedVerifierConstraints x) -> { + System.out.println(x); + }); + } + + static OldA get_OldA_from_NewB() { + return new NewB(); + } + + static void run(MyIntf intf) { + intf.function(new OldClassWithExcludedVerifierConstraints()); + } +} + +interface MyIntf { + public void function(OldClassWithExcludedVerifierConstraints x); +} + +class NewB extends OldA {} + +class MyEvent extends Event {} +class MyEvent2 extends MyEvent {} + +class NewClassWithExcludedVerifierConstraints { + static MyEvent get_MyEvent_from_MyEvent2() { + return new MyEvent2(); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldClassWithExcludedVerifierConstraints.jasm b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldClassWithExcludedVerifierConstraints.jasm new file mode 100644 index 00000000000..0c0556bf122 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldClassWithExcludedVerifierConstraints.jasm @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025, 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. + * + */ + +// This old class has a verification constraint that "MyEvent must be a subtype of Event". However, +// Event and all of its subtypes are excluded from the AOT cache, so this class must also be excluded. + +super public class OldClassWithExcludedVerifierConstraints + version 49:0 +{ + + +public Method "":"()V" + stack 1 locals 1 +{ + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; +} + +static Method get_Event_from_MyEvent:"()Ljdk/jfr/Event;" + stack 2 locals 0 +{ + new class MyEvent; + dup; + invokespecial Method MyEvent."":"()V"; + areturn; +} + +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldClassWithVerifierConstraints.jasm b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldClassWithVerifierConstraints.jasm new file mode 100644 index 00000000000..946c51050a3 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldClassWithVerifierConstraints.jasm @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025, 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. + * + */ + +// This old class as a verification constraint that "NewB must be a subtype of OldA". Since both +// OldA and NewB are not excluded, then this class should be cached in aot-linked state. + +super public class OldClassWithVerifierConstraints + version 49:0 +{ + + +public Method "":"()V" + stack 1 locals 1 +{ + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; +} + +static Method get_OldA_from_NewB:"()LOldA;" + stack 2 locals 0 +{ + new class NewB; + dup; + invokespecial Method NewB."":"()V"; + areturn; +} + +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTClassLinkingVerification.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTClassLinkingVerification.java new file mode 100644 index 00000000000..050f7d28585 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTClassLinkingVerification.java @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2023, 2025, 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 8317269 + * @requires vm.cds + * @requires vm.cds.supports.aot.class.linking + * @summary Test for verification of classes that are aot-linked + * @library /test/jdk/lib/testlibrary + * /test/lib + * /test/hotspot/jtreg/runtime/cds/appcds + * /test/hotspot/jtreg/runtime/cds/appcds/test-classes + * @build GoodOldClass + * BadOldClass BadOldClass2 BadOldClass3 BadOldClass4 + * BadNewClass BadNewClass2 BadNewClass3 BadNewClass4 + * @build AOTClassLinkingVerification + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar WhiteBox.jar jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app1.jar + * AOTClassLinkingVerificationApp + * Unlinked UnlinkedSuper + * BadOldClass + * BadOldClass2 + * BadOldClass3 + * BadOldClass4 + * BadNewClass + * BadNewClass2 + * BadNewClass3 + * BadNewClass4 + * GoodOldClass Vehicle Car + * Util + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app2.jar + * Foo NotFoo + * UnlinkedSub + * @run driver AOTClassLinkingVerification + */ + +import java.io.File; +import java.lang.invoke.MethodHandles; +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.whitebox.WhiteBox; + +public class AOTClassLinkingVerification { + static final String app1Jar = ClassFileInstaller.getJarPath("app1.jar"); + static final String app2Jar = ClassFileInstaller.getJarPath("app2.jar"); + static final String wbJar = TestCommon.getTestJar("WhiteBox.jar"); + static final String bootAppendWhiteBox = "-Xbootclasspath/a:" + wbJar; + static final String mainClass = AOTClassLinkingVerificationApp.class.getName(); + + static class Tester extends CDSAppTester { + public Tester(String testName) { + super(testName); + } + + @Override + public String[] vmArgs(RunMode runMode) { + if (runMode == RunMode.TRAINING || + runMode == RunMode.ASSEMBLY) { + return new String[] { + "-XX:+AOTClassLinking", "-Xlog:cds+class=debug", bootAppendWhiteBox, + }; + } else { + return new String[] { + "-XX:+UnlockDiagnosticVMOptions", "-XX:+WhiteBoxAPI", bootAppendWhiteBox, + }; + } + } + + @Override + public String classpath(RunMode runMode) { + if (runMode == RunMode.TRAINING || + runMode == RunMode.ASSEMBLY) { + return app1Jar; + } else { + return app1Jar + File.pathSeparator + app2Jar; + } + } + + @Override + public String[] appCommandLine(RunMode runMode) { + if (runMode == RunMode.TRAINING || + runMode == RunMode.ASSEMBLY) { + return new String[] { + "AOTClassLinkingVerificationApp", app1Jar, "ASSEMBLY" + }; + } else { + return new String[] { + "AOTClassLinkingVerificationApp", app1Jar, "PRODUCTION" + }; + } + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception { + if (runMode == RunMode.TRAINING) { + out.shouldContain("Preload Warning: Verification failed for BadNewClass"); + out.shouldContain("Preload Warning: Verification failed for BadNewClass2"); + out.shouldContain("Preload Warning: Verification failed for BadNewClass3"); + out.shouldContain("Preload Warning: Verification failed for BadNewClass4"); + out.shouldContain("Preload Warning: Verification failed for BadOldClass"); + out.shouldContain("Preload Warning: Verification failed for BadOldClass2"); + out.shouldContain("Preload Warning: Verification failed for BadOldClass3"); + out.shouldContain("Preload Warning: Verification failed for BadOldClass4"); + out.shouldContain("Preload Warning: Verification failed for Unlinked"); + } + } + } + + public static void main(String[] args) throws Exception { + // Dump without app2.jar so: + // - Unlinked can be resolved, but UnlinkedSuper UnlinkedSub cannot be resolved, + // so Unlinked cannot be verified at dump time. + // - BadOldClass2 can be resolved, but Foo and NotFoo cannot be resolved, + // so BadOldClass2 cannot be verified at dump time. + // - BadNewClass2 can be resolved, but Foo and NotFoo cannot be resolved, + // so BadNewClass2 cannot be verified at dump time. + Tester t1 = new Tester("verification-aot-linked-classes"); + t1.run("AOT"); + } +} + +class AOTClassLinkingVerificationApp { + static WhiteBox wb = WhiteBox.getWhiteBox(); + static ClassLoader classLoader = AOTClassLinkingVerificationApp.class.getClassLoader(); + static File app1Jar; + static boolean isProduction; + public static void main(String[] args) throws Exception { + app1Jar = new File(args[0]); + isProduction = args[1].equals("PRODUCTION"); + if (isProduction) { + assertNotShared(UnlinkedSub.class); + assertShared(UnlinkedSuper.class); + assertNotShared(Unlinked.class); // failed verification during dump time + assertNotShared(Foo.class); + assertNotShared(NotFoo.class); + } + String s = null; + try { + s = Unlinked.doit(); + } catch (NoClassDefFoundError ncdfe) { + // UnlinkedSub is in app2Jar but only app1Jar is used during training + // and assembly phases. So NoClassDefFoundError is expected during + // during training and assembly phases. + if (isProduction) { + throw ncdfe; + } + } + if (isProduction && !s.equals("heyhey")) { + throw new RuntimeException("Unlinked.doit() returns wrong result: " + s); + } + + // =============================================================================== + + checkSimpleBadClass("BadOldClass"); + + Class cls_BadOldClass2 = Class.forName("BadOldClass2", false, classLoader); + if (isProduction) { + assertNotShared(cls_BadOldClass2); // failed verification during dump time + } + try { + cls_BadOldClass2.newInstance(); + throw new RuntimeException("BadOldClass2 cannot be verified"); + } catch (NoClassDefFoundError ncdfe) { + // BadOldClass2 loads Foo and NotFoo which is in app2Jar which is used + // only in production run. + if (isProduction) { + throw ncdfe; + } + } catch (VerifyError expected) {} + + checkSimpleBadClass("BadOldClass3"); + checkSimpleBadClass("BadOldClass4"); + + // =============================================================================== + + checkSimpleBadClass("BadNewClass"); + + Class cls_BadNewClass2 = Class.forName("BadNewClass2", false, classLoader); + if (isProduction) { + assertNotShared(cls_BadNewClass2); // failed verification during dump time + } + try { + cls_BadNewClass2.newInstance(); + throw new RuntimeException("BadNewClass2 cannot be verified"); + } catch (NoClassDefFoundError ncdfe) { + // BadNewClass2 loads Foo and NotFoo which is in app2Jar which is used + // only in production run. + if (isProduction) { + throw ncdfe; + } + } catch (VerifyError expected) {} + + checkSimpleBadClass("BadNewClass3"); + checkSimpleBadClass("BadNewClass4"); + + // =============================================================================== + + if (isProduction) { + assertAlreadyLoaded("Vehicle"); + assertAlreadyLoaded("Car"); + assertAlreadyLoaded("GoodOldClass"); + + assertShared(GoodOldClass.class); + assertShared(Vehicle.class); + assertShared(Car.class); + } + + GoodOldClass.doit(); // Should not fail + } + + static void checkSimpleBadClass(String className) throws Exception { + Class cls = Class.forName(className, false, classLoader); + if (isProduction) { + assertNotShared(cls); // failed verification during dump time + } + try { + cls.newInstance(); + throw new RuntimeException(className + " should not pass verification"); + } catch (VerifyError expected) {} + } + + static void assertShared(Class c) { + if (!wb.isSharedClass(c)) { + throw new RuntimeException("wb.isSharedClass(" + c.getName() + ") should be true"); + } + } + + static void assertNotShared(Class c) { + if (wb.isSharedClass(c)) { + throw new RuntimeException("wb.isSharedClass(" + c.getName() + ") should be false"); + } + } + + static void assertAlreadyLoaded(String className) throws Exception { + byte[] data = Util.getClassFileFromJar(app1Jar, className); + try { + MethodHandles.lookup().defineClass(data); + } catch (LinkageError e) { + if (e.getMessage().contains("duplicate class definition for " + className)) { + return; + } else { + throw e; + } + } + throw new RuntimeException(className + " must have already been loaded"); + } +} + + +class Unlinked { + static String doit() { + UnlinkedSuper sup = new UnlinkedSub(); + return sup.doit(); + } +} + +abstract class UnlinkedSuper { + abstract String doit(); +} + +class UnlinkedSub extends UnlinkedSuper { + String doit() { + return "heyhey"; + } +} + +class Foo {} +class NotFoo {} + +class Vehicle {} +class Car extends Vehicle {} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadNewClass.jasm b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadNewClass.jasm new file mode 100644 index 00000000000..cf71d209819 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadNewClass.jasm @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2025, 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. + * + */ + +super public class BadNewClass + version 52:0 +{ + +public Method "":"()V" + stack 1 locals 1 +{ + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; +} + + /* + * The following method tries to return an Object as a String. + * Verifier should fail. + */ +public Method doit:"()Ljava/lang/String;" + stack 2 locals 1 +{ + new class java/lang/Object; + dup; + invokespecial Method java/lang/Object."":"()V"; + astore_0; + aload_0; + areturn; // tries to return an Object as a String +} + +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadNewClass2.jasm b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadNewClass2.jasm new file mode 100644 index 00000000000..c243d583484 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadNewClass2.jasm @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2025, 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. + * + */ + +super public class BadNewClass2 + version 52:0 +{ + +public Method "":"()V" + stack 1 locals 1 +{ + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; +} + + /* + * The following method tries to return a NotFoo as a Foo. + * Verifier should fail. + */ +public Method doit:"()LFoo;" + stack 2 locals 1 +{ + new class NotFoo; + dup; + invokespecial Method NotFoo."":"()V"; + astore_0; + aload_0; + areturn; // tries to return a NotFoo as a Foo +} + +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadNewClass3.jasm b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadNewClass3.jasm new file mode 100644 index 00000000000..afce8f76ed8 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadNewClass3.jasm @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025, 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. + * + */ + +super public class BadNewClass3 + version 52:0 +{ + +public Method "":"()V" + stack 1 locals 1 +{ + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; +} + + /* + * The following method tries to return a String[][] as an Integer[]. + * Verifier should fail. + * + * Note: the arrays must have different number of dimensions, or else + * the new verifier will just check the "bottom" classes. I.e., String and Integer + */ +public Method doit:"()[Ljava/lang/Integer;" + stack 2 locals 1 +{ + iconst_1; + iconst_1; + multianewarray class "[[Ljava/lang/String;", 2; + areturn; +} + +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadNewClass4.jasm b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadNewClass4.jasm new file mode 100644 index 00000000000..afebe3f1f8e --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadNewClass4.jasm @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025, 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. + * + */ + +super public class BadNewClass4 + version 52:0 +{ + +public Method "":"()V" + stack 1 locals 1 +{ + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; +} + + /* + * The following method tries to return a String[][] as an Integer[][]. + * Verifier should fail. + * + * Note: the new verifier looks up the Integer and String types, + * not the array types. + */ +public Method doit:"()[[Ljava/lang/Integer;" + stack 2 locals 1 +{ + iconst_1; + iconst_1; + multianewarray class "[[Ljava/lang/String;", 2; + areturn; +} + +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadOldClass.jasm b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadOldClass.jasm new file mode 100644 index 00000000000..adc6a50d4ba --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadOldClass.jasm @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2025, 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. + * + */ + +super public class BadOldClass + version 49:0 +{ + +public Method "":"()V" + stack 1 locals 1 +{ + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; +} + + /* + * The following method tries to return an Object as a String. + * Verifier should fail. + */ +public Method doit:"()Ljava/lang/String;" + stack 2 locals 1 +{ + new class java/lang/Object; + dup; + invokespecial Method java/lang/Object."":"()V"; + astore_0; + aload_0; + areturn; // tries to return an Object as a String +} + +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadOldClass2.jasm b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadOldClass2.jasm new file mode 100644 index 00000000000..1808a019ace --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadOldClass2.jasm @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2025, 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. + * + */ + +super public class BadOldClass2 + version 49:0 +{ + +public Method "":"()V" + stack 1 locals 1 +{ + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; +} + + /* + * The following method tries to return a NotFoo as a Foo. + * Verifier should fail. + */ +public Method doit:"()LFoo;" + stack 2 locals 1 +{ + new class NotFoo; + dup; + invokespecial Method NotFoo."":"()V"; + astore_0; + aload_0; + areturn; // tries to return a NotFoo as a Foo +} + +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadOldClass3.jasm b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadOldClass3.jasm new file mode 100644 index 00000000000..6e943cf5afc --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadOldClass3.jasm @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025, 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. + * + */ + +super public class BadOldClass3 + version 49:0 +{ + +public Method "":"()V" + stack 1 locals 1 +{ + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; +} + + /* + * The following method tries to return a String[][] as an Integer[]. + * Verifier should fail. + * + * Note: the arrays have different number of dimensions. The old verifier + * rejects this immediately without looking up the String/Integer types. + */ +public Method doit:"()[Ljava/lang/Integer;" + stack 2 locals 1 +{ + iconst_1; + iconst_1; + multianewarray class "[[Ljava/lang/String;", 2; + areturn; +} + +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadOldClass4.jasm b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadOldClass4.jasm new file mode 100644 index 00000000000..56f2a8d299a --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BadOldClass4.jasm @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025, 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. + * + */ + +super public class BadOldClass4 + version 49:0 +{ + +public Method "":"()V" + stack 1 locals 1 +{ + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; +} + + /* + * The following method tries to return a String[][] as an Integer[][]. + * Verifier should fail. + * + * Note: the old verifier looks up the Integer and String types, + * not the array types. + */ +public Method doit:"()[[Ljava/lang/Integer;" + stack 2 locals 1 +{ + iconst_1; + iconst_1; + multianewarray class "[[Ljava/lang/String;", 2; + areturn; +} + +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java index 0f7707edae3..e1f5f548593 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java @@ -147,7 +147,7 @@ public class BulkLoaderTest { @Override public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception { if (isAOTWorkflow() && runMode == RunMode.TRAINING) { - out.shouldContain("Skipping BadOldClassA: Unlinked class not supported by AOTConfiguration"); + out.shouldContain("Skipping BadOldClassA: Failed verification"); out.shouldContain("Skipping SimpleCusty: Duplicated unregistered class"); } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/GoodOldClass.jasm b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/GoodOldClass.jasm new file mode 100644 index 00000000000..92a79380d93 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/GoodOldClass.jasm @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025, 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. + * + */ + +super public class GoodOldClass + version 49:0 +{ + +public Method "":"()V" + stack 1 locals 1 +{ + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; +} + + +public static Method doit:"()LVehicle;" + stack 2 locals 1 +{ + new class Car; + dup; + invokespecial Method Car."":"()V"; + astore_0; + aload_0; + areturn; // tries to return a Car as a Vehicle +} + +}