From 6350c3641e2a6cbb15aaaf2f62ebd2007eca3954 Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Mon, 30 Mar 2026 23:14:13 +0000 Subject: [PATCH] 8377703: Assert that all AOT heap objects have valid classes Reviewed-by: kvn, eosterlund --- src/hotspot/share/cds/archiveUtils.hpp | 38 +++++++++++++++++++++++++- src/hotspot/share/cds/heapShared.cpp | 25 +++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/cds/archiveUtils.hpp b/src/hotspot/share/cds/archiveUtils.hpp index 84ad8e6bdf3..42455adedd0 100644 --- a/src/hotspot/share/cds/archiveUtils.hpp +++ b/src/hotspot/share/cds/archiveUtils.hpp @@ -36,6 +36,8 @@ #include "runtime/semaphore.hpp" #include "utilities/bitMap.hpp" #include "utilities/exceptions.hpp" +#include "utilities/growableArray.hpp" +#include "utilities/hashTable.hpp" #include "utilities/macros.hpp" class BootstrapInfo; @@ -44,7 +46,6 @@ class ReservedSpace; class VirtualSpace; template class Array; -template class GrowableArray; // ArchivePtrMarker is used to mark the location of pointers embedded in a CDS archive. E.g., when an // InstanceKlass k is dumped, we mark the location of the k->_name pointer by effectively calling @@ -401,4 +402,39 @@ public: void run_task(ArchiveWorkerTask* task); }; +// A utility class for writing an array of unique items into the +// AOT cache. For determinism, the order of the array is the same +// as calls to add(). I.e., if items are added in the order +// of A, B, A, C, B, D, then the array will be written as {A, B, C, D} +template +class ArchivableTable : public AnyObj { + using Table = HashTable; + Table* _seen_items; + GrowableArray* _ordered_array; +public: + ArchivableTable() { + _seen_items = new (mtClassShared)Table(); + _ordered_array = new (mtClassShared)GrowableArray(128, mtClassShared); + } + + ~ArchivableTable() { + delete _seen_items; + delete _ordered_array; + } + + void add(T t) { + bool created; + _seen_items->put_if_absent(t, &created); + if (created) { + _ordered_array->append(t); + } + } + + Array* write_ordered_array() { + return ArchiveUtils::archive_array(_ordered_array); + } +}; + +using ArchivableKlassTable = ArchivableTable; + #endif // SHARE_CDS_ARCHIVEUTILS_HPP diff --git a/src/hotspot/share/cds/heapShared.cpp b/src/hotspot/share/cds/heapShared.cpp index 3d10e7e1f88..f721b4b370c 100644 --- a/src/hotspot/share/cds/heapShared.cpp +++ b/src/hotspot/share/cds/heapShared.cpp @@ -112,6 +112,11 @@ static Klass* _test_class = nullptr; static const ArchivedKlassSubGraphInfoRecord* _test_class_record = nullptr; #endif +#ifdef ASSERT +// All classes that have at least one instance in the cached heap. +static ArchivableKlassTable* _dumptime_classes_with_cached_oops = nullptr; +static Array* _runtime_classes_with_cached_oops = nullptr; +#endif // // If you add new entries to the following tables, you should know what you're doing! @@ -391,6 +396,21 @@ void HeapShared::initialize_streaming() { } void HeapShared::enable_gc() { +#ifdef ASSERT + // At this point, a GC may start and will be able to see some or all + // of the cached oops. The class of each oop seen by the GC must have + // already been loaded. One function with such a requirement is + // ClaimMetadataVisitingOopIterateClosure::do_klass(). + if (is_archived_heap_in_use()) { + Array* klasses = _runtime_classes_with_cached_oops; + + for (int i = 0; i < klasses->length(); i++) { + assert(klasses->at(i)->class_loader_data() != nullptr, + "class of cached oop must have been loaded"); + } + } +#endif + if (AOTStreamedHeapLoader::is_in_use()) { AOTStreamedHeapLoader::enable_gc(); } @@ -567,6 +587,7 @@ bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgra AOTArtifactFinder::add_cached_class(obj->klass()); AOTOopChecker::check(obj); // Make sure contents of this oop are safe. count_allocation(obj->size()); + DEBUG_ONLY(_dumptime_classes_with_cached_oops->add(obj->klass())); if (HeapShared::is_writing_streaming_mode()) { AOTStreamedHeapWriter::add_source_obj(obj); @@ -686,6 +707,7 @@ void HeapShared::init_dumping() { _scratch_objects_table = new (mtClass)MetaspaceObjToOopHandleTable(); _pending_roots = new GrowableArrayCHeap(500); _pending_roots->append(nullptr); // root index 0 represents a null oop + DEBUG_ONLY(_dumptime_classes_with_cached_oops = new (mtClassShared)ArchivableKlassTable()); } void HeapShared::init_scratch_objects_for_basic_type_mirrors(TRAPS) { @@ -967,6 +989,8 @@ void HeapShared::write_heap(AOTMappedHeapInfo* mapped_heap_info, AOTStreamedHeap ArchiveBuilder::OtherROAllocMark mark; write_subgraph_info_table(); + DEBUG_ONLY(_runtime_classes_with_cached_oops = _dumptime_classes_with_cached_oops->write_ordered_array()); + delete _pending_roots; _pending_roots = nullptr; @@ -1278,6 +1302,7 @@ void HeapShared::serialize_tables(SerializeClosure* soc) { _run_time_subgraph_info_table.serialize_header(soc); soc->do_ptr(&_run_time_special_subgraph); + DEBUG_ONLY(soc->do_ptr(&_runtime_classes_with_cached_oops)); } static void verify_the_heap(Klass* k, const char* which) {