8293980: Resolve CONSTANT_FieldRef at CDS dump time

Reviewed-by: erikj, matsaave, heidinga
This commit is contained in:
Ioi Lam 2024-06-14 06:06:24 +00:00
parent eb2488fd17
commit b818679eba
35 changed files with 1117 additions and 171 deletions

@ -62,6 +62,15 @@ ifeq ($(EXTERNAL_BUILDJDK), true)
INTERIM_IMAGE_DIR := $(BUILD_JDK)
endif
# These are needed for deterministic classlist:
# - The classlist can be influenced by locale. Always set it to en/US.
# - Run with -Xint, as the compiler can speculatively resolve constant pool entries.
# - ForkJoinPool parallelism can cause constant pool resolution to be non-deterministic.
CLASSLIST_FILE_VM_OPTS = \
-Duser.language=en -Duser.country=US \
-Xint \
-Djava.util.concurrent.ForkJoinPool.common.parallelism=0
# Save the stderr output of the command and print it along with stdout in case
# something goes wrong.
$(CLASSLIST_FILE): $(INTERIM_IMAGE_DIR)/bin/java$(EXECUTABLE_SUFFIX) $(CLASSLIST_JAR)
@ -69,7 +78,7 @@ $(CLASSLIST_FILE): $(INTERIM_IMAGE_DIR)/bin/java$(EXECUTABLE_SUFFIX) $(CLASSLIST
$(call LogInfo, Generating $(patsubst $(OUTPUTDIR)/%, %, $@))
$(call LogInfo, Generating $(patsubst $(OUTPUTDIR)/%, %, $(JLI_TRACE_FILE)))
$(FIXPATH) $(INTERIM_IMAGE_DIR)/bin/java -XX:DumpLoadedClassList=$@.raw \
-Duser.language=en -Duser.country=US \
$(CLASSLIST_FILE_VM_OPTS) \
-cp $(SUPPORT_OUTPUTDIR)/classlist.jar \
build.tools.classlist.HelloClasslist $(LOG_DEBUG)
$(GREP) -v HelloClasslist $@.raw > $@.interim
@ -79,7 +88,7 @@ $(CLASSLIST_FILE): $(INTERIM_IMAGE_DIR)/bin/java$(EXECUTABLE_SUFFIX) $(CLASSLIST
$(FIXPATH) $(INTERIM_IMAGE_DIR)/bin/java -XX:DumpLoadedClassList=$@.raw.2 \
-XX:SharedClassListFile=$@.interim -XX:SharedArchiveFile=$@.jsa \
-Djava.lang.invoke.MethodHandle.TRACE_RESOLVE=true \
-Duser.language=en -Duser.country=US \
$(CLASSLIST_FILE_VM_OPTS) \
--module-path $(SUPPORT_OUTPUTDIR)/classlist.jar \
-cp $(SUPPORT_OUTPUTDIR)/classlist.jar \
build.tools.classlist.HelloClasslist \

@ -717,6 +717,14 @@ void ArchiveBuilder::write_pointer_in_buffer(address* ptr_location, address src_
}
}
void ArchiveBuilder::mark_and_relocate_to_buffered_addr(address* ptr_location) {
assert(*ptr_location != nullptr, "sanity");
if (!is_in_mapped_static_archive(*ptr_location)) {
*ptr_location = get_buffered_addr(*ptr_location);
}
ArchivePtrMarker::mark_pointer(ptr_location);
}
address ArchiveBuilder::get_buffered_addr(address src_addr) const {
SourceObjInfo* p = _src_obj_table.get(src_addr);
assert(p != nullptr, "src_addr " INTPTR_FORMAT " is used but has not been archived",
@ -755,6 +763,16 @@ void ArchiveBuilder::make_klasses_shareable() {
int num_obj_array_klasses = 0;
int num_type_array_klasses = 0;
for (int i = 0; i < klasses()->length(); i++) {
// Some of the code in ConstantPool::remove_unshareable_info() requires the classes
// to be in linked state, so it must be call here before the next loop, which returns
// all classes to unlinked state.
Klass* k = get_buffered_addr(klasses()->at(i));
if (k->is_instance_klass()) {
InstanceKlass::cast(k)->constants()->remove_unshareable_info();
}
}
for (int i = 0; i < klasses()->length(); i++) {
const char* type;
const char* unlinked = "";

@ -408,6 +408,11 @@ public:
write_pointer_in_buffer((address*)ptr_location, (address)src_addr);
}
void mark_and_relocate_to_buffered_addr(address* ptr_location);
template <typename T> void mark_and_relocate_to_buffered_addr(T ptr_location) {
mark_and_relocate_to_buffered_addr((address*)ptr_location);
}
address get_buffered_addr(address src_addr) const;
template <typename T> T get_buffered_addr(T src_addr) const {
return (T)get_buffered_addr((address)src_addr);

@ -357,7 +357,7 @@ void ArchiveUtils::log_to_classlist(BootstrapInfo* bootstrap_specifier, TRAPS) {
ResourceMark rm(THREAD);
int pool_index = bootstrap_specifier->bss_index();
ClassListWriter w;
w.stream()->print("%s %s", LAMBDA_PROXY_TAG, pool->pool_holder()->name()->as_C_string());
w.stream()->print("%s %s", ClassListParser::lambda_proxy_tag(), pool->pool_holder()->name()->as_C_string());
CDSIndyInfo cii;
ClassListParser::populate_cds_indy_info(pool, pool_index, &cii, CHECK);
GrowableArray<const char*>* indy_items = cii.items();

@ -25,6 +25,7 @@
#include "precompiled.hpp"
#include "cds/archiveUtils.hpp"
#include "cds/classListParser.hpp"
#include "cds/classPrelinker.hpp"
#include "cds/lambdaFormInvokers.hpp"
#include "cds/metaspaceShared.hpp"
#include "cds/unregisteredClasses.hpp"
@ -52,6 +53,10 @@
#include "utilities/macros.hpp"
#include "utilities/utf8.hpp"
const char* ClassListParser::CONSTANT_POOL_TAG = "@cp";
const char* ClassListParser::LAMBDA_FORM_TAG = "@lambda-form-invoker";
const char* ClassListParser::LAMBDA_PROXY_TAG = "@lambda-proxy";
volatile Thread* ClassListParser::_parsing_thread = nullptr;
ClassListParser* ClassListParser::_instance = nullptr;
@ -299,6 +304,9 @@ void ClassListParser::parse_at_tags(TRAPS) {
}
} else if (strcmp(_token, LAMBDA_FORM_TAG) == 0) {
LambdaFormInvokers::append(os::strdup((const char*)(_line + offset), mtInternal));
} else if (strcmp(_token, CONSTANT_POOL_TAG) == 0) {
_token = _line + offset;
parse_constant_pool_tag();
} else {
error("Invalid @ tag at the beginning of line \"%s\" line #%zu", _token, lineno());
}
@ -395,9 +403,14 @@ void ClassListParser::print_actual_interfaces(InstanceKlass* ik) {
jio_fprintf(defaultStream::error_stream(), "}\n");
}
void ClassListParser::error(const char* msg, ...) {
void ClassListParser::print_diagnostic_info(outputStream* st, const char* msg, ...) {
va_list ap;
va_start(ap, msg);
print_diagnostic_info(st, msg, ap);
va_end(ap);
}
void ClassListParser::print_diagnostic_info(outputStream* st, const char* msg, va_list ap) {
int error_index = pointer_delta_as_int(_token, _line);
if (error_index >= _line_len) {
error_index = _line_len - 1;
@ -412,25 +425,34 @@ void ClassListParser::error(const char* msg, ...) {
jio_vfprintf(defaultStream::error_stream(), msg, ap);
if (_line_len <= 0) {
jio_fprintf(defaultStream::error_stream(), "\n");
st->print("\n");
} else {
jio_fprintf(defaultStream::error_stream(), ":\n");
st->print(":\n");
for (int i=0; i<_line_len; i++) {
char c = _line[i];
if (c == '\0') {
jio_fprintf(defaultStream::error_stream(), "%s", " ");
st->print("%s", " ");
} else {
jio_fprintf(defaultStream::error_stream(), "%c", c);
st->print("%c", c);
}
}
jio_fprintf(defaultStream::error_stream(), "\n");
st->print("\n");
for (int i=0; i<error_index; i++) {
jio_fprintf(defaultStream::error_stream(), "%s", " ");
st->print("%s", " ");
}
jio_fprintf(defaultStream::error_stream(), "^\n");
st->print("^\n");
}
va_end(ap);
}
void ClassListParser::error(const char* msg, ...) {
va_list ap;
va_start(ap, msg);
fileStream fs(defaultStream::error_stream());
//TODO: we should write to UL/error instead, but that requires fixing some tests cases.
//LogTarget(Error, cds) lt;
//LogStream ls(lt);
print_diagnostic_info(&fs, msg, ap);
va_end(ap);
vm_exit_during_initialization("class list format error.", nullptr);
}
@ -453,6 +475,16 @@ void ClassListParser::check_class_name(const char* class_name) {
}
}
void ClassListParser::constant_pool_resolution_warning(const char* msg, ...) {
va_list ap;
va_start(ap, msg);
LogTarget(Warning, cds, resolve) lt;
LogStream ls(lt);
print_diagnostic_info(&ls, msg, ap);
ls.print("Your classlist may be out of sync with the JDK or the application.");
va_end(ap);
}
// This function is used for loading classes for customized class loaders
// during archive dumping.
InstanceKlass* ClassListParser::load_class_from_source(Symbol* class_name, TRAPS) {
@ -727,3 +759,92 @@ InstanceKlass* ClassListParser::lookup_interface_for_current_class(Symbol* inter
ShouldNotReachHere();
return nullptr;
}
InstanceKlass* ClassListParser::find_builtin_class_helper(JavaThread* current, Symbol* class_name_symbol, oop class_loader_oop) {
Handle class_loader(current, class_loader_oop);
Handle protection_domain;
return SystemDictionary::find_instance_klass(current, class_name_symbol, class_loader, protection_domain);
}
InstanceKlass* ClassListParser::find_builtin_class(JavaThread* current, const char* class_name) {
TempNewSymbol class_name_symbol = SymbolTable::new_symbol(class_name);
InstanceKlass* ik;
if ( (ik = find_builtin_class_helper(current, class_name_symbol, nullptr)) != nullptr
|| (ik = find_builtin_class_helper(current, class_name_symbol, SystemDictionary::java_platform_loader())) != nullptr
|| (ik = find_builtin_class_helper(current, class_name_symbol, SystemDictionary::java_system_loader())) != nullptr) {
return ik;
} else {
return nullptr;
}
}
void ClassListParser::parse_constant_pool_tag() {
if (parse_lambda_forms_invokers_only()) {
return;
}
JavaThread* THREAD = JavaThread::current();
skip_whitespaces();
char* class_name = _token;
skip_non_whitespaces();
*_token = '\0';
_token ++;
InstanceKlass* ik = find_builtin_class(THREAD, class_name);
if (ik == nullptr) {
_token = class_name;
if (strstr(class_name, "/$Proxy") != nullptr ||
strstr(class_name, "MethodHandle$Species_") != nullptr) {
// ignore -- TODO: we should filter these out in classListWriter.cpp
} else {
constant_pool_resolution_warning("class %s is not (yet) loaded by one of the built-in loaders", class_name);
}
return;
}
ResourceMark rm(THREAD);
constantPoolHandle cp(THREAD, ik->constants());
GrowableArray<bool> preresolve_list(cp->length(), cp->length(), false);
bool preresolve_class = false;
bool preresolve_fmi = false;
bool preresolve_indy = false;
while (*_token) {
int cp_index;
skip_whitespaces();
parse_uint(&cp_index);
if (cp_index < 1 || cp_index >= cp->length()) {
constant_pool_resolution_warning("Invalid constant pool index %d", cp_index);
return;
} else {
preresolve_list.at_put(cp_index, true);
}
constantTag cp_tag = cp->tag_at(cp_index);
switch (cp_tag.value()) {
case JVM_CONSTANT_UnresolvedClass:
preresolve_class = true;
break;
case JVM_CONSTANT_UnresolvedClassInError:
case JVM_CONSTANT_Class:
// ignore
break;
case JVM_CONSTANT_Fieldref:
preresolve_fmi = true;
break;
break;
default:
constant_pool_resolution_warning("Unsupported constant pool index %d: %s (type=%d)",
cp_index, cp_tag.internal_name(), cp_tag.value());
return;
}
}
if (preresolve_class) {
ClassPrelinker::preresolve_class_cp_entries(THREAD, ik, &preresolve_list);
}
if (preresolve_fmi) {
ClassPrelinker::preresolve_field_and_method_cp_entries(THREAD, ik, &preresolve_list);
}
}

@ -31,9 +31,6 @@
#include "utilities/istream.hpp"
#include "utilities/resizeableResourceHash.hpp"
#define LAMBDA_PROXY_TAG "@lambda-proxy"
#define LAMBDA_FORM_TAG "@lambda-form-invoker"
class constantPoolHandle;
class Thread;
@ -68,6 +65,10 @@ public:
};
class ClassListParser : public StackObj {
static const char* CONSTANT_POOL_TAG;
static const char* LAMBDA_FORM_TAG;
static const char* LAMBDA_PROXY_TAG;
public:
enum ParseMode {
_parse_all,
@ -117,17 +118,25 @@ private:
void print_actual_interfaces(InstanceKlass *ik);
bool is_matching_cp_entry(const constantPoolHandle &pool, int cp_index, TRAPS);
InstanceKlass* find_builtin_class_helper(JavaThread* current, Symbol* class_name_symbol, oop class_loader_oop);
InstanceKlass* find_builtin_class(JavaThread* current, const char* class_name);
void resolve_indy(JavaThread* current, Symbol* class_name_symbol);
void resolve_indy_impl(Symbol* class_name_symbol, TRAPS);
void clean_up_input_line();
void read_class_name_and_attributes();
void parse_class_name_and_attributes(TRAPS);
Klass* load_current_class(Symbol* class_name_symbol, TRAPS);
void parse_constant_pool_tag();
size_t lineno() { return _input_stream.lineno(); }
FILE* do_open(const char* file);
ClassListParser(const char* file, ParseMode _parse_mode);
~ClassListParser();
void print_diagnostic_info(outputStream* st, const char* msg, va_list ap) ATTRIBUTE_PRINTF(3, 0);
void print_diagnostic_info(outputStream* st, const char* msg, ...) ATTRIBUTE_PRINTF(3, 0);
void constant_pool_resolution_warning(const char* msg, ...) ATTRIBUTE_PRINTF(2, 0);
void error(const char* msg, ...) ATTRIBUTE_PRINTF(2, 0);
public:
static void parse_classlist(const char* classlist_path, ParseMode parse_mode, TRAPS) {
@ -141,13 +150,18 @@ public:
assert(_instance != nullptr, "must be");
return _instance;
}
static const char* lambda_proxy_tag() {
return LAMBDA_PROXY_TAG;
}
static const char* lambda_form_tag() {
return LAMBDA_FORM_TAG;
}
void parse(TRAPS);
void split_tokens_by_whitespace(int offset, GrowableArray<const char*>* items);
int split_at_tag_from_line();
void parse_at_tags(TRAPS);
char* _token;
void error(const char* msg, ...);
void parse_int(int* value);
void parse_uint(int* value);
bool try_parse_uint(int* value);

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2024, 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
@ -25,12 +25,15 @@
#include "precompiled.hpp"
#include "cds/cds_globals.hpp"
#include "cds/classListWriter.hpp"
#include "cds/lambdaFormInvokers.inline.hpp"
#include "classfile/classFileStream.hpp"
#include "classfile/classLoader.hpp"
#include "classfile/classLoaderData.hpp"
#include "classfile/classLoaderDataGraph.hpp"
#include "classfile/moduleEntry.hpp"
#include "classfile/systemDictionaryShared.hpp"
#include "memory/resourceArea.hpp"
#include "oops/constantPool.inline.hpp"
#include "oops/instanceKlass.hpp"
#include "runtime/mutexLocker.hpp"
@ -189,3 +192,94 @@ void ClassListWriter::delete_classlist() {
delete _classlist_file;
}
}
class ClassListWriter::WriteResolveConstantsCLDClosure : public CLDClosure {
public:
void do_cld(ClassLoaderData* cld) {
for (Klass* klass = cld->klasses(); klass != nullptr; klass = klass->next_link()) {
if (klass->is_instance_klass()) {
InstanceKlass* ik = InstanceKlass::cast(klass);
write_resolved_constants_for(ik);
}
}
}
};
void ClassListWriter::write_resolved_constants() {
if (!is_enabled()) {
return;
}
MutexLocker lock(ClassLoaderDataGraph_lock);
MutexLocker lock2(ClassListFile_lock, Mutex::_no_safepoint_check_flag);
WriteResolveConstantsCLDClosure closure;
ClassLoaderDataGraph::loaded_cld_do(&closure);
}
void ClassListWriter::write_resolved_constants_for(InstanceKlass* ik) {
if (!SystemDictionaryShared::is_builtin_loader(ik->class_loader_data()) ||
ik->is_hidden()) {
return;
}
if (LambdaFormInvokers::may_be_regenerated_class(ik->name())) {
return;
}
if (ik->name()->equals("jdk/internal/module/SystemModules$all")) {
// This class is regenerated during JDK build process, so the classlist
// may not match the version that's in the real jdk image.
return;
}
if (!has_id(ik)) { // do not resolve CP for classes loaded by custom loaders.
return;
}
ResourceMark rm;
ConstantPool* cp = ik->constants();
GrowableArray<bool> list(cp->length(), cp->length(), false);
bool print = false;
for (int cp_index = 1; cp_index < cp->length(); cp_index++) { // Index 0 is unused
switch (cp->tag_at(cp_index).value()) {
case JVM_CONSTANT_Class:
{
Klass* k = cp->resolved_klass_at(cp_index);
if (k->is_instance_klass()) {
list.at_put(cp_index, true);
print = true;
}
}
break;
}
}
if (cp->cache() != nullptr) {
Array<ResolvedFieldEntry>* field_entries = cp->cache()->resolved_field_entries();
if (field_entries != nullptr) {
for (int i = 0; i < field_entries->length(); i++) {
ResolvedFieldEntry* rfe = field_entries->adr_at(i);
if (rfe->is_resolved(Bytecodes::_getstatic) ||
rfe->is_resolved(Bytecodes::_putstatic) ||
rfe->is_resolved(Bytecodes::_getfield) ||
rfe->is_resolved(Bytecodes::_putfield)) {
list.at_put(rfe->constant_pool_index(), true);
print = true;
}
}
}
}
if (print) {
outputStream* stream = _classlist_file;
stream->print("@cp %s", ik->name()->as_C_string());
for (int i = 0; i < list.length(); i++) {
if (list.at(i)) {
constantTag cp_tag = cp->tag_at(i).value();
assert(cp_tag.value() == JVM_CONSTANT_Class ||
cp_tag.value() == JVM_CONSTANT_Fieldref, "sanity");
stream->print(" %d", i);
}
}
stream->cr();
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2024, 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
@ -34,6 +34,8 @@ class ClassFileStream;
class ClassListWriter {
#if INCLUDE_CDS
class IDTable;
class WriteResolveConstantsCLDClosure;
static fileStream* _classlist_file;
static IDTable* _id_table;
static int _total_ids;
@ -42,6 +44,7 @@ class ClassListWriter {
static int get_id(const InstanceKlass* k);
static bool has_id(const InstanceKlass* k);
static void assert_locked() { assert_lock_strong(ClassListFile_lock); }
static void write_resolved_constants_for(InstanceKlass* klass);
public:
ClassListWriter() : _locker(Thread::current(), ClassListFile_lock, Mutex::_no_safepoint_check_flag) {}
@ -66,6 +69,7 @@ public:
static void init() NOT_CDS_RETURN;
static void write(const InstanceKlass* k, const ClassFileStream* cfs) NOT_CDS_RETURN;
static void write_to_stream(const InstanceKlass* k, outputStream* stream, const ClassFileStream* cfs = nullptr) NOT_CDS_RETURN;
static void write_resolved_constants() NOT_CDS_RETURN;
static void delete_classlist() NOT_CDS_RETURN;
};

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 2024, 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
@ -26,8 +26,12 @@
#include "cds/archiveBuilder.hpp"
#include "cds/cdsConfig.hpp"
#include "cds/classPrelinker.hpp"
#include "cds/regeneratedClasses.hpp"
#include "classfile/systemDictionary.hpp"
#include "classfile/systemDictionaryShared.hpp"
#include "classfile/vmClasses.hpp"
#include "interpreter/bytecodeStream.hpp"
#include "interpreter/interpreterRuntime.hpp"
#include "memory/resourceArea.hpp"
#include "oops/constantPool.inline.hpp"
#include "oops/instanceKlass.hpp"
@ -73,33 +77,52 @@ void ClassPrelinker::dispose() {
_processed_classes = nullptr;
}
bool ClassPrelinker::can_archive_resolved_klass(ConstantPool* cp, int cp_index) {
// Returns true if we CAN PROVE that cp_index will always resolve to
// the same information at both dump time and run time. This is a
// necessary (but not sufficient) condition for pre-resolving cp_index
// during CDS archive assembly.
bool ClassPrelinker::is_resolution_deterministic(ConstantPool* cp, int cp_index) {
assert(!is_in_archivebuilder_buffer(cp), "sanity");
assert(cp->tag_at(cp_index).is_klass(), "must be resolved");
Klass* resolved_klass = cp->resolved_klass_at(cp_index);
assert(resolved_klass != nullptr, "must be");
if (cp->tag_at(cp_index).is_klass()) {
// We require cp_index to be already resolved. This is fine for now, are we
// currently archive only CP entries that are already resolved.
Klass* resolved_klass = cp->resolved_klass_at(cp_index);
return resolved_klass != nullptr && is_class_resolution_deterministic(cp->pool_holder(), resolved_klass);
} else if (cp->tag_at(cp_index).is_field()) {
int klass_cp_index = cp->uncached_klass_ref_index_at(cp_index);
if (!cp->tag_at(klass_cp_index).is_klass()) {
// Not yet resolved
return false;
}
Klass* k = cp->resolved_klass_at(klass_cp_index);
if (!is_class_resolution_deterministic(cp->pool_holder(), k)) {
return false;
}
return can_archive_resolved_klass(cp->pool_holder(), resolved_klass);
if (!k->is_instance_klass()) {
// TODO: support non instance klasses as well.
return false;
}
// Here, We don't check if this entry can actually be resolved to a valid Field/Method.
// This method should be called by the ConstantPool to check Fields/Methods that
// have already been successfully resolved.
return true;
} else {
return false;
}
}
bool ClassPrelinker::can_archive_resolved_klass(InstanceKlass* cp_holder, Klass* resolved_klass) {
bool ClassPrelinker::is_class_resolution_deterministic(InstanceKlass* cp_holder, Klass* resolved_class) {
assert(!is_in_archivebuilder_buffer(cp_holder), "sanity");
assert(!is_in_archivebuilder_buffer(resolved_klass), "sanity");
assert(!is_in_archivebuilder_buffer(resolved_class), "sanity");
if (resolved_klass->is_instance_klass()) {
InstanceKlass* ik = InstanceKlass::cast(resolved_klass);
if (is_vm_class(ik)) { // These are safe to resolve. See is_vm_class declaration.
assert(ik->is_shared_boot_class(), "vmClasses must be loaded by boot loader");
if (cp_holder->is_shared_boot_class()) {
// For now, do this for boot loader only. Other loaders
// must go through ConstantPool::klass_at_impl at runtime
// to put this class in their directory.
if (resolved_class->is_instance_klass()) {
InstanceKlass* ik = InstanceKlass::cast(resolved_class);
// TODO: we can support the platform and app loaders as well, if we
// preload the vmClasses into these two loaders during VM bootstrap.
return true;
}
if (!ik->is_shared() && SystemDictionaryShared::is_excluded_class(ik)) {
return false;
}
if (cp_holder->is_subtype_of(ik)) {
@ -108,20 +131,34 @@ bool ClassPrelinker::can_archive_resolved_klass(InstanceKlass* cp_holder, Klass*
return true;
}
// TODO -- allow objArray classes, too
if (is_vm_class(ik)) {
if (ik->class_loader() != cp_holder->class_loader()) {
// At runtime, cp_holder() may not be able to resolve to the same
// ik. For example, a different version of ik may be defined in
// cp->pool_holder()'s loader using MethodHandles.Lookup.defineClass().
return false;
} else {
return true;
}
}
} else if (resolved_class->is_objArray_klass()) {
Klass* elem = ObjArrayKlass::cast(resolved_class)->bottom_klass();
if (elem->is_instance_klass()) {
return is_class_resolution_deterministic(cp_holder, InstanceKlass::cast(elem));
} else if (elem->is_typeArray_klass()) {
return true;
}
} else if (resolved_class->is_typeArray_klass()) {
return true;
}
return false;
}
void ClassPrelinker::dumptime_resolve_constants(InstanceKlass* ik, TRAPS) {
constantPoolHandle cp(THREAD, ik->constants());
if (cp->cache() == nullptr || cp->reference_map() == nullptr) {
// The cache may be null if the pool_holder klass fails verification
// at dump time due to missing dependencies.
if (!ik->is_linked()) {
return;
}
bool first_time;
_processed_classes->put_if_absent(ik, &first_time);
if (!first_time) {
@ -129,12 +166,9 @@ void ClassPrelinker::dumptime_resolve_constants(InstanceKlass* ik, TRAPS) {
return;
}
constantPoolHandle cp(THREAD, ik->constants());
for (int cp_index = 1; cp_index < cp->length(); cp_index++) { // Index 0 is unused
switch (cp->tag_at(cp_index).value()) {
case JVM_CONSTANT_UnresolvedClass:
maybe_resolve_class(cp, cp_index, CHECK);
break;
case JVM_CONSTANT_String:
resolve_string(cp, cp_index, CHECK); // may throw OOM when interning strings.
break;
@ -142,43 +176,33 @@ void ClassPrelinker::dumptime_resolve_constants(InstanceKlass* ik, TRAPS) {
}
}
Klass* ClassPrelinker::find_loaded_class(JavaThread* THREAD, oop class_loader, Symbol* name) {
HandleMark hm(THREAD);
Handle h_loader(THREAD, class_loader);
Klass* k = SystemDictionary::find_instance_or_array_klass(THREAD, name,
// This works only for the boot/platform/app loaders
Klass* ClassPrelinker::find_loaded_class(Thread* current, oop class_loader, Symbol* name) {
HandleMark hm(current);
Handle h_loader(current, class_loader);
Klass* k = SystemDictionary::find_instance_or_array_klass(current, name,
h_loader,
Handle());
if (k != nullptr) {
return k;
}
if (class_loader == SystemDictionary::java_system_loader()) {
return find_loaded_class(THREAD, SystemDictionary::java_platform_loader(), name);
} else if (class_loader == SystemDictionary::java_platform_loader()) {
return find_loaded_class(THREAD, nullptr, name);
if (h_loader() == SystemDictionary::java_system_loader()) {
return find_loaded_class(current, SystemDictionary::java_platform_loader(), name);
} else if (h_loader() == SystemDictionary::java_platform_loader()) {
return find_loaded_class(current, nullptr, name);
} else {
assert(h_loader() == nullptr, "This function only works for boot/platform/app loaders %p %p %p",
cast_from_oop<address>(h_loader()),
cast_from_oop<address>(SystemDictionary::java_system_loader()),
cast_from_oop<address>(SystemDictionary::java_platform_loader()));
}
return nullptr;
}
Klass* ClassPrelinker::maybe_resolve_class(constantPoolHandle cp, int cp_index, TRAPS) {
assert(!is_in_archivebuilder_buffer(cp()), "sanity");
InstanceKlass* cp_holder = cp->pool_holder();
if (!cp_holder->is_shared_boot_class() &&
!cp_holder->is_shared_platform_class() &&
!cp_holder->is_shared_app_class()) {
// Don't trust custom loaders, as they may not be well-behaved
// when resolving classes.
return nullptr;
}
Symbol* name = cp->klass_name_at(cp_index);
Klass* resolved_klass = find_loaded_class(THREAD, cp_holder->class_loader(), name);
if (resolved_klass != nullptr && can_archive_resolved_klass(cp_holder, resolved_klass)) {
Klass* k = cp->klass_at(cp_index, CHECK_NULL); // Should fail only with OOM
assert(k == resolved_klass, "must be");
}
return resolved_klass;
Klass* ClassPrelinker::find_loaded_class(Thread* current, ConstantPool* cp, int class_cp_index) {
Symbol* name = cp->klass_name_at(class_cp_index);
return find_loaded_class(current, cp->pool_holder()->class_loader(), name);
}
#if INCLUDE_CDS_JAVA_HEAP
@ -190,6 +214,110 @@ void ClassPrelinker::resolve_string(constantPoolHandle cp, int cp_index, TRAPS)
}
#endif
void ClassPrelinker::preresolve_class_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray<bool>* preresolve_list) {
if (!SystemDictionaryShared::is_builtin_loader(ik->class_loader_data())) {
return;
}
JavaThread* THREAD = current;
constantPoolHandle cp(THREAD, ik->constants());
for (int cp_index = 1; cp_index < cp->length(); cp_index++) {
if (cp->tag_at(cp_index).value() == JVM_CONSTANT_UnresolvedClass) {
if (preresolve_list != nullptr && preresolve_list->at(cp_index) == false) {
// This class was not resolved during trial run. Don't attempt to resolve it. Otherwise
// the compiler may generate less efficient code.
continue;
}
if (find_loaded_class(current, cp(), cp_index) == nullptr) {
// Do not resolve any class that has not been loaded yet
continue;
}
Klass* resolved_klass = cp->klass_at(cp_index, THREAD);
if (HAS_PENDING_EXCEPTION) {
CLEAR_PENDING_EXCEPTION; // just ignore
} else {
log_trace(cds, resolve)("Resolved class [%3d] %s -> %s", cp_index, ik->external_name(),
resolved_klass->external_name());
}
}
}
}
void ClassPrelinker::preresolve_field_and_method_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray<bool>* preresolve_list) {
JavaThread* THREAD = current;
constantPoolHandle cp(THREAD, ik->constants());
if (cp->cache() == nullptr) {
return;
}
for (int i = 0; i < ik->methods()->length(); i++) {
Method* m = ik->methods()->at(i);
BytecodeStream bcs(methodHandle(THREAD, m));
while (!bcs.is_last_bytecode()) {
bcs.next();
Bytecodes::Code raw_bc = bcs.raw_code();
switch (raw_bc) {
case Bytecodes::_getfield:
case Bytecodes::_putfield:
maybe_resolve_fmi_ref(ik, m, raw_bc, bcs.get_index_u2(), preresolve_list, THREAD);
if (HAS_PENDING_EXCEPTION) {
CLEAR_PENDING_EXCEPTION; // just ignore
}
break;
default:
break;
}
}
}
}
void ClassPrelinker::maybe_resolve_fmi_ref(InstanceKlass* ik, Method* m, Bytecodes::Code bc, int raw_index,
GrowableArray<bool>* preresolve_list, TRAPS) {
methodHandle mh(THREAD, m);
constantPoolHandle cp(THREAD, ik->constants());
HandleMark hm(THREAD);
int cp_index = cp->to_cp_index(raw_index, bc);
if (cp->is_resolved(raw_index, bc)) {
return;
}
if (preresolve_list != nullptr && preresolve_list->at(cp_index) == false) {
// This field wasn't resolved during the trial run. Don't attempt to resolve it. Otherwise
// the compiler may generate less efficient code.
return;
}
int klass_cp_index = cp->uncached_klass_ref_index_at(cp_index);
if (find_loaded_class(THREAD, cp(), klass_cp_index) == nullptr) {
// Do not resolve any field/methods from a class that has not been loaded yet.
return;
}
Klass* resolved_klass = cp->klass_ref_at(raw_index, bc, CHECK);
switch (bc) {
case Bytecodes::_getfield:
case Bytecodes::_putfield:
InterpreterRuntime::resolve_get_put(bc, raw_index, mh, cp, false /*initialize_holder*/, CHECK);
break;
default:
ShouldNotReachHere();
}
if (log_is_enabled(Trace, cds, resolve)) {
ResourceMark rm(THREAD);
bool resolved = cp->is_resolved(raw_index, bc);
Symbol* name = cp->name_ref_at(raw_index, bc);
Symbol* signature = cp->signature_ref_at(raw_index, bc);
log_trace(cds, resolve)("%s %s [%3d] %s -> %s.%s:%s",
(resolved ? "Resolved" : "Failed to resolve"),
Bytecodes::name(bc), cp_index, ik->external_name(),
resolved_klass->external_name(),
name->as_C_string(), signature->as_C_string());
}
}
#ifdef ASSERT
bool ClassPrelinker::is_in_archivebuilder_buffer(address p) {
if (!Thread::current()->is_VM_thread() || ArchiveBuilder::current() == nullptr) {

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 2024, 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
@ -25,6 +25,7 @@
#ifndef SHARE_CDS_CLASSPRELINKER_HPP
#define SHARE_CDS_CLASSPRELINKER_HPP
#include "interpreter/bytecodes.hpp"
#include "oops/oopsHierarchy.hpp"
#include "memory/allStatic.hpp"
#include "memory/allocation.hpp"
@ -64,14 +65,21 @@ class ClassPrelinker : AllStatic {
return is_in_archivebuilder_buffer((address)(p));
}
static void resolve_string(constantPoolHandle cp, int cp_index, TRAPS) NOT_CDS_JAVA_HEAP_RETURN;
static Klass* maybe_resolve_class(constantPoolHandle cp, int cp_index, TRAPS);
static bool can_archive_resolved_klass(InstanceKlass* cp_holder, Klass* resolved_klass);
static Klass* find_loaded_class(JavaThread* THREAD, oop class_loader, Symbol* name);
static bool is_class_resolution_deterministic(InstanceKlass* cp_holder, Klass* resolved_class);
static Klass* find_loaded_class(Thread* current, oop class_loader, Symbol* name);
static Klass* find_loaded_class(Thread* current, ConstantPool* cp, int class_cp_index);
// fmi = FieldRef/MethodRef/InterfaceMethodRef
static void maybe_resolve_fmi_ref(InstanceKlass* ik, Method* m, Bytecodes::Code bc, int raw_index,
GrowableArray<bool>* resolve_fmi_list, TRAPS);
public:
static void initialize();
static void dispose();
static void preresolve_class_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray<bool>* preresolve_list);
static void preresolve_field_and_method_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray<bool>* preresolve_list);
// Is this class resolved as part of vmClasses::resolve_all()? If so, these
// classes are guatanteed to be loaded at runtime (and cannot be replaced by JVMTI)
// when CDS is enabled. Therefore, we can safely keep a direct reference to these
@ -82,10 +90,7 @@ public:
// CDS archive.
static void dumptime_resolve_constants(InstanceKlass* ik, TRAPS);
// Can we resolve the klass entry at cp_index in this constant pool, and store
// the result in the CDS archive? Returns true if cp_index is guaranteed to
// resolve to the same InstanceKlass* at both dump time and run time.
static bool can_archive_resolved_klass(ConstantPool* cp, int cp_index);
static bool is_resolution_deterministic(ConstantPool* cp, int cp_index);
};
#endif // SHARE_CDS_CLASSPRELINKER_HPP

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2024, 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
@ -102,8 +102,12 @@ void DumpAllocStats::print_stats(int ro_all, int rw_all) {
#undef fmt_stats
msg.debug("Class CP entries = %d, archived = %d (%3.1f%%)",
_num_klass_cp_entries, _num_klass_cp_entries_archived,
percent_of(_num_klass_cp_entries_archived, _num_klass_cp_entries));
msg.info("Class CP entries = %6d, archived = %6d (%5.1f%%), reverted = %6d",
_num_klass_cp_entries, _num_klass_cp_entries_archived,
percent_of(_num_klass_cp_entries_archived, _num_klass_cp_entries),
_num_klass_cp_entries_reverted);
msg.info("Field CP entries = %6d, archived = %6d (%5.1f%%), reverted = %6d",
_num_field_cp_entries, _num_field_cp_entries_archived,
percent_of(_num_field_cp_entries_archived, _num_field_cp_entries),
_num_field_cp_entries_reverted);
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2024, 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
@ -65,8 +65,12 @@ public:
int _counts[2][_number_of_types];
int _bytes [2][_number_of_types];
int _num_field_cp_entries;
int _num_field_cp_entries_archived;
int _num_field_cp_entries_reverted;
int _num_klass_cp_entries;
int _num_klass_cp_entries_archived;
int _num_klass_cp_entries_reverted;
public:
enum { RO = 0, RW = 1 };
@ -74,8 +78,12 @@ public:
DumpAllocStats() {
memset(_counts, 0, sizeof(_counts));
memset(_bytes, 0, sizeof(_bytes));
_num_klass_cp_entries = 0;
_num_klass_cp_entries_archived = 0;
_num_field_cp_entries = 0;
_num_field_cp_entries_archived = 0;
_num_field_cp_entries_reverted = 0;
_num_klass_cp_entries = 0;
_num_klass_cp_entries_archived = 0;
_num_klass_cp_entries_reverted = 0;
};
CompactHashtableStats* symbol_stats() { return &_symbol_stats; }
@ -102,9 +110,16 @@ public:
_bytes[RW][CppVTablesType] += byte_size;
}
void record_klass_cp_entry(bool archived) {
void record_field_cp_entry(bool archived, bool reverted) {
_num_field_cp_entries ++;
_num_field_cp_entries_archived += archived ? 1 : 0;
_num_field_cp_entries_reverted += reverted ? 1 : 0;
}
void record_klass_cp_entry(bool archived, bool reverted) {
_num_klass_cp_entries ++;
_num_klass_cp_entries_archived += archived ? 1 : 0;
_num_klass_cp_entries_reverted += reverted ? 1 : 0;
}
void print_stats(int ro_all, int rw_all);

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2024, 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
@ -32,6 +32,7 @@
class ClassFileStream;
template <class T>
class Array;
class SerializeClosure;
class LambdaFormInvokers : public AllStatic {
private:
@ -46,5 +47,6 @@ class LambdaFormInvokers : public AllStatic {
static void regenerate_holder_classes(TRAPS);
static void serialize(SerializeClosure* soc);
static void cleanup_regenerated_classes();
inline static bool may_be_regenerated_class(Symbol* name);
};
#endif // SHARE_CDS_LAMBDAFORMINVOKERS_HPP

@ -0,0 +1,38 @@
/*
* Copyright (c) 2023, 2024, 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.
*
*/
#ifndef SHARE_CDS_LAMBDAFORMINVOKERS_INLINE_HPP
#define SHARE_CDS_LAMBDAFORMINVOKERS_INLINE_HPP
#include "cds/lambdaFormInvokers.hpp"
#include "classfile/vmSymbols.hpp"
inline bool LambdaFormInvokers::may_be_regenerated_class(Symbol* name) {
return name == vmSymbols::java_lang_invoke_Invokers_Holder() ||
name == vmSymbols::java_lang_invoke_DirectMethodHandle_Holder() ||
name == vmSymbols::java_lang_invoke_LambdaForm_Holder() ||
name == vmSymbols::java_lang_invoke_DelegatingMethodHandle_Holder();
}
#endif // SHARE_CDS_LAMBDAFORMINVOKERS_INLINE_HPP

@ -645,27 +645,31 @@ JRT_END
//
void InterpreterRuntime::resolve_get_put(JavaThread* current, Bytecodes::Code bytecode) {
// resolve field
fieldDescriptor info;
LastFrameAccessor last_frame(current);
constantPoolHandle pool(current, last_frame.method()->constants());
methodHandle m(current, last_frame.method());
resolve_get_put(bytecode, last_frame.get_index_u2(bytecode), m, pool, true /*initialize_holder*/, current);
}
void InterpreterRuntime::resolve_get_put(Bytecodes::Code bytecode, int field_index,
methodHandle& m,
constantPoolHandle& pool,
bool initialize_holder, TRAPS) {
fieldDescriptor info;
bool is_put = (bytecode == Bytecodes::_putfield || bytecode == Bytecodes::_nofast_putfield ||
bytecode == Bytecodes::_putstatic);
bool is_static = (bytecode == Bytecodes::_getstatic || bytecode == Bytecodes::_putstatic);
int field_index = last_frame.get_index_u2(bytecode);
{
JvmtiHideSingleStepping jhss(current);
JavaThread* THREAD = current; // For exception macros.
JvmtiHideSingleStepping jhss(THREAD);
LinkResolver::resolve_field_access(info, pool, field_index,
m, bytecode, CHECK);
m, bytecode, initialize_holder, CHECK);
} // end JvmtiHideSingleStepping
// check if link resolution caused cpCache to be updated
if (pool->resolved_field_entry_at(field_index)->is_resolved(bytecode)) return;
// compute auxiliary field attributes
TosState state = as_TosState(info.field_type());

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2024, 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,7 +91,12 @@ class InterpreterRuntime: AllStatic {
static void throw_pending_exception(JavaThread* current);
static void resolve_from_cache(JavaThread* current, Bytecodes::Code bytecode);
private:
// Used by ClassListParser.
static void resolve_get_put(Bytecodes::Code bytecode, int field_index,
methodHandle& m, constantPoolHandle& pool, bool initialize_holder, TRAPS);
private:
// Statics & fields
static void resolve_get_put(JavaThread* current, Bytecodes::Code bytecode);

@ -974,9 +974,14 @@ void LinkResolver::check_field_accessability(Klass* ref_klass,
}
}
void LinkResolver::resolve_field_access(fieldDescriptor& fd, const constantPoolHandle& pool, int index, const methodHandle& method, Bytecodes::Code byte, TRAPS) {
void LinkResolver::resolve_field_access(fieldDescriptor& fd,
const constantPoolHandle& pool,
int index,
const methodHandle& method,
Bytecodes::Code byte,
bool initialize_class, TRAPS) {
LinkInfo link_info(pool, index, method, byte, CHECK);
resolve_field(fd, link_info, byte, true, CHECK);
resolve_field(fd, link_info, byte, initialize_class, CHECK);
}
void LinkResolver::resolve_field(fieldDescriptor& fd,

@ -293,7 +293,16 @@ class LinkResolver: AllStatic {
const constantPoolHandle& pool,
int index,
const methodHandle& method,
Bytecodes::Code byte, TRAPS);
Bytecodes::Code byte,
bool initialize_class, TRAPS);
static void resolve_field_access(fieldDescriptor& result,
const constantPoolHandle& pool,
int index,
const methodHandle& method,
Bytecodes::Code byte, TRAPS) {
resolve_field_access(result, pool, index, method, byte,
/* initialize_class*/true, THREAD);
}
static void resolve_field(fieldDescriptor& result, const LinkInfo& link_info,
Bytecodes::Code access_kind,
bool initialize_class, TRAPS);

@ -300,20 +300,22 @@ objArrayOop ConstantPool::prepare_resolved_references_for_archiving() {
objArrayOop rr = resolved_references();
if (rr != nullptr) {
ConstantPool* orig_pool = ArchiveBuilder::current()->get_source_addr(this);
objArrayOop scratch_rr = HeapShared::scratch_resolved_references(orig_pool);
int rr_len = rr->length();
ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(this);
objArrayOop scratch_rr = HeapShared::scratch_resolved_references(src_cp);
Array<u2>* ref_map = reference_map();
int ref_map_len = ref_map == nullptr ? 0 : ref_map->length();
int rr_len = rr->length();
for (int i = 0; i < rr_len; i++) {
oop obj = rr->obj_at(i);
scratch_rr->obj_at_put(i, nullptr);
if (obj != nullptr && i < ref_map_len) {
int index = object_to_cp_index(i);
if (tag_at(index).is_string()) {
assert(java_lang_String::is_instance(obj), "must be");
if (!ArchiveHeapWriter::is_string_too_large_to_archive(obj)) {
scratch_rr->obj_at_put(i, obj);
if (obj != nullptr) {
if (i < ref_map_len) {
int index = object_to_cp_index(i);
if (tag_at(index).is_string()) {
assert(java_lang_String::is_instance(obj), "must be");
if (!ArchiveHeapWriter::is_string_too_large_to_archive(obj)) {
scratch_rr->obj_at_put(i, obj);
}
}
}
}
@ -386,23 +388,61 @@ void ConstantPool::remove_unshareable_info() {
// we always set _on_stack to true to avoid having to change _flags during runtime.
_flags |= (_on_stack | _is_shared);
if (!_pool_holder->is_linked() && !_pool_holder->verified_at_dump_time()) {
return;
// resolved_references(): remember its length. If it cannot be restored
// from the archived heap objects at run time, we need to dynamically allocate it.
if (cache() != nullptr) {
set_resolved_reference_length(
resolved_references() != nullptr ? resolved_references()->length() : 0);
set_resolved_references(OopHandle());
}
// Resolved references are not in the shared archive.
// Save the length for restoration. It is not necessarily the same length
// as reference_map.length() if invokedynamic is saved. It is needed when
// re-creating the resolved reference array if archived heap data cannot be map
// at runtime.
set_resolved_reference_length(
resolved_references() != nullptr ? resolved_references()->length() : 0);
set_resolved_references(OopHandle());
remove_unshareable_entries();
}
bool archived = false;
static const char* get_type(Klass* k) {
const char* type;
Klass* src_k;
if (ArchiveBuilder::is_active() && ArchiveBuilder::current()->is_in_buffer_space(k)) {
src_k = ArchiveBuilder::current()->get_source_addr(k);
} else {
src_k = k;
}
if (src_k->is_objArray_klass()) {
src_k = ObjArrayKlass::cast(src_k)->bottom_klass();
assert(!src_k->is_objArray_klass(), "sanity");
}
if (src_k->is_typeArray_klass()) {
type = "prim";
} else {
InstanceKlass* src_ik = InstanceKlass::cast(src_k);
oop loader = src_ik->class_loader();
if (loader == nullptr) {
type = "boot";
} else if (loader == SystemDictionary::java_platform_loader()) {
type = "plat";
} else if (loader == SystemDictionary::java_system_loader()) {
type = "app";
} else {
type = "unreg";
}
}
return type;
}
void ConstantPool::remove_unshareable_entries() {
ResourceMark rm;
log_info(cds, resolve)("Archiving CP entries for %s", pool_holder()->name()->as_C_string());
for (int cp_index = 1; cp_index < length(); cp_index++) { // cp_index 0 is unused
switch (tag_at(cp_index).value()) {
int cp_tag = tag_at(cp_index).value();
switch (cp_tag) {
case JVM_CONSTANT_UnresolvedClass:
ArchiveBuilder::alloc_stats()->record_klass_cp_entry(false, false);
break;
case JVM_CONSTANT_UnresolvedClassInError:
tag_at_put(cp_index, JVM_CONSTANT_UnresolvedClass);
ArchiveBuilder::alloc_stats()->record_klass_cp_entry(false, true);
break;
case JVM_CONSTANT_MethodHandleInError:
tag_at_put(cp_index, JVM_CONSTANT_MethodHandle);
@ -414,8 +454,9 @@ void ConstantPool::remove_unshareable_info() {
tag_at_put(cp_index, JVM_CONSTANT_Dynamic);
break;
case JVM_CONSTANT_Class:
archived = maybe_archive_resolved_klass_at(cp_index);
ArchiveBuilder::alloc_stats()->record_klass_cp_entry(archived);
remove_resolved_klass_if_non_deterministic(cp_index);
break;
default:
break;
}
}
@ -426,40 +467,46 @@ void ConstantPool::remove_unshareable_info() {
}
}
bool ConstantPool::maybe_archive_resolved_klass_at(int cp_index) {
void ConstantPool::remove_resolved_klass_if_non_deterministic(int cp_index) {
assert(ArchiveBuilder::current()->is_in_buffer_space(this), "must be");
assert(tag_at(cp_index).is_klass(), "must be resolved");
if (pool_holder()->is_hidden() && cp_index == pool_holder()->this_class_index()) {
// All references to a hidden class's own field/methods are through this
// index, which was resolved in ClassFileParser::fill_instance_klass. We
// must preserve it.
return true;
Klass* k = resolved_klass_at(cp_index);
bool can_archive;
if (k == nullptr) {
// We'd come here if the referenced class has been excluded via
// SystemDictionaryShared::is_excluded_class(). As a result, ArchiveBuilder
// has cleared the resolved_klasses()->at(...) pointer to NULL. Thus, we
// need to revert the tag to JVM_CONSTANT_UnresolvedClass.
can_archive = false;
} else {
ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(this);
can_archive = ClassPrelinker::is_resolution_deterministic(src_cp, cp_index);
}
CPKlassSlot kslot = klass_slot_at(cp_index);
int resolved_klass_index = kslot.resolved_klass_index();
Klass* k = resolved_klasses()->at(resolved_klass_index);
// k could be null if the referenced class has been excluded via
// SystemDictionaryShared::is_excluded_class().
if (!can_archive) {
int resolved_klass_index = klass_slot_at(cp_index).resolved_klass_index();
resolved_klasses()->at_put(resolved_klass_index, nullptr);
tag_at_put(cp_index, JVM_CONSTANT_UnresolvedClass);
}
if (k != nullptr) {
ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(this);
if (ClassPrelinker::can_archive_resolved_klass(src_cp, cp_index)) {
if (log_is_enabled(Debug, cds, resolve)) {
ResourceMark rm;
log_debug(cds, resolve)("Resolved klass CP entry [%d]: %s => %s", cp_index,
pool_holder()->external_name(), k->external_name());
}
return true;
LogStreamHandle(Trace, cds, resolve) log;
if (log.is_enabled()) {
ResourceMark rm;
log.print("%s klass CP entry [%3d]: %s %s",
(can_archive ? "archived" : "reverted"),
cp_index, pool_holder()->name()->as_C_string(), get_type(pool_holder()));
if (can_archive) {
log.print(" => %s %s%s", k->name()->as_C_string(), get_type(k),
(!k->is_instance_klass() || pool_holder()->is_subtype_of(k)) ? "" : " (not supertype)");
} else {
Symbol* name = klass_name_at(cp_index);
log.print(" %s", name->as_C_string());
}
}
// This referenced class cannot be archived. Revert the tag to UnresolvedClass,
// so that the proper class loading and initialization can happen at runtime.
resolved_klasses()->at_put(resolved_klass_index, nullptr);
tag_at_put(cp_index, JVM_CONSTANT_UnresolvedClass);
return false;
ArchiveBuilder::alloc_stats()->record_klass_cp_entry(can_archive, /*reverted=*/!can_archive);
}
#endif // INCLUDE_CDS
@ -707,6 +754,31 @@ int ConstantPool::to_cp_index(int index, Bytecodes::Code code) {
}
}
bool ConstantPool::is_resolved(int index, Bytecodes::Code code) {
assert(cache() != nullptr, "'index' is a rewritten index so this class must have been rewritten");
switch(code) {
case Bytecodes::_invokedynamic:
return resolved_indy_entry_at(index)->is_resolved();
case Bytecodes::_getfield:
case Bytecodes::_getstatic:
case Bytecodes::_putfield:
case Bytecodes::_putstatic:
return resolved_field_entry_at(index)->is_resolved(code);
case Bytecodes::_invokeinterface:
case Bytecodes::_invokehandle:
case Bytecodes::_invokespecial:
case Bytecodes::_invokestatic:
case Bytecodes::_invokevirtual:
case Bytecodes::_fast_invokevfinal: // Bytecode interpreter uses this
return resolved_method_entry_at(index)->is_resolved(code);
default:
fatal("Unexpected bytecode: %s", Bytecodes::name(code));
}
}
u2 ConstantPool::uncached_name_and_type_ref_index_at(int cp_index) {
if (tag_at(cp_index).has_bootstrap()) {
u2 pool_index = bootstrap_name_and_type_ref_index_at(cp_index);

@ -661,6 +661,8 @@ class ConstantPool : public Metadata {
int to_cp_index(int which, Bytecodes::Code code);
bool is_resolved(int which, Bytecodes::Code code);
// Lookup for entries consisting of (name_index, signature_index)
u2 name_ref_index_at(int cp_index); // == low-order jshort of name_and_type_at(cp_index)
u2 signature_ref_index_at(int cp_index); // == high-order jshort of name_and_type_at(cp_index)
@ -677,9 +679,11 @@ class ConstantPool : public Metadata {
// CDS support
objArrayOop prepare_resolved_references_for_archiving() NOT_CDS_JAVA_HEAP_RETURN_(nullptr);
void add_dumped_interned_strings() NOT_CDS_JAVA_HEAP_RETURN;
bool maybe_archive_resolved_klass_at(int cp_index);
void remove_unshareable_info();
void restore_unshareable_info(TRAPS);
private:
void remove_unshareable_entries();
void remove_resolved_klass_if_non_deterministic(int cp_index);
#endif
private:

@ -25,6 +25,7 @@
#include "precompiled.hpp"
#include "cds/archiveBuilder.hpp"
#include "cds/cdsConfig.hpp"
#include "cds/classPrelinker.hpp"
#include "cds/heapShared.hpp"
#include "classfile/resolutionErrors.hpp"
#include "classfile/systemDictionary.hpp"
@ -388,18 +389,14 @@ void ConstantPoolCache::record_gc_epoch() {
#if INCLUDE_CDS
void ConstantPoolCache::remove_unshareable_info() {
assert(CDSConfig::is_dumping_archive(), "sanity");
// <this> is the copy to be written into the archive. It's in the ArchiveBuilder's "buffer space".
// However, this->_initial_entries was not copied/relocated by the ArchiveBuilder, so it's
// still pointing to the array allocated inside save_for_archive().
if (_resolved_indy_entries != nullptr) {
for (int i = 0; i < _resolved_indy_entries->length(); i++) {
resolved_indy_entry_at(i)->remove_unshareable_info();
}
}
if (_resolved_field_entries != nullptr) {
for (int i = 0; i < _resolved_field_entries->length(); i++) {
resolved_field_entry_at(i)->remove_unshareable_info();
}
remove_resolved_field_entries_if_non_deterministic();
}
if (_resolved_method_entries != nullptr) {
for (int i = 0; i < _resolved_method_entries->length(); i++) {
@ -407,6 +404,41 @@ void ConstantPoolCache::remove_unshareable_info() {
}
}
}
void ConstantPoolCache::remove_resolved_field_entries_if_non_deterministic() {
ConstantPool* cp = constant_pool();
ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(cp);
for (int i = 0; i < _resolved_field_entries->length(); i++) {
ResolvedFieldEntry* rfi = _resolved_field_entries->adr_at(i);
int cp_index = rfi->constant_pool_index();
bool archived = false;
bool resolved = rfi->is_resolved(Bytecodes::_getfield) ||
rfi->is_resolved(Bytecodes::_putfield);
if (resolved && ClassPrelinker::is_resolution_deterministic(src_cp, cp_index)) {
rfi->mark_and_relocate();
archived = true;
} else {
rfi->remove_unshareable_info();
}
if (resolved) {
LogStreamHandle(Trace, cds, resolve) log;
if (log.is_enabled()) {
ResourceMark rm;
int klass_cp_index = cp->uncached_klass_ref_index_at(cp_index);
Symbol* klass_name = cp->klass_name_at(klass_cp_index);
Symbol* name = cp->uncached_name_ref_at(cp_index);
Symbol* signature = cp->uncached_signature_ref_at(cp_index);
log.print("%s field CP entry [%3d]: %s %s %s.%s:%s",
(archived ? "archived" : "reverted"),
cp_index,
cp->pool_holder()->name()->as_C_string(),
(archived ? "=>" : " "),
klass_name->as_C_string(), name->as_C_string(), signature->as_C_string());
}
}
ArchiveBuilder::alloc_stats()->record_field_cp_entry(archived, resolved && !archived);
}
}
#endif // INCLUDE_CDS
void ConstantPoolCache::deallocate_contents(ClassLoaderData* data) {

@ -193,14 +193,12 @@ class ConstantPoolCache: public MetaspaceObj {
#if INCLUDE_CDS
void remove_unshareable_info();
void save_for_archive(TRAPS);
#endif
public:
static int size() { return align_metadata_size(sizeof(ConstantPoolCache) / wordSize); }
private:
// Helpers
ConstantPool** constant_pool_addr() { return &_constant_pool; }
@ -224,6 +222,10 @@ class ConstantPoolCache: public MetaspaceObj {
void dump_cache();
#endif // INCLUDE_JVMTI
#if INCLUDE_CDS
void remove_resolved_field_entries_if_non_deterministic();
#endif
// RedefineClasses support
DEBUG_ONLY(bool on_stack() { return false; })
void deallocate_contents(ClassLoaderData* data);

@ -2557,7 +2557,9 @@ void InstanceKlass::remove_unshareable_info() {
init_implementor();
}
constants()->remove_unshareable_info();
// Call remove_unshareable_info() on other objects that belong to this class, except
// for constants()->remove_unshareable_info(), which is called in a separate pass in
// ArchiveBuilder::make_klasses_shareable(),
for (int i = 0; i < methods()->length(); i++) {
Method* m = methods()->at(i);

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2024, 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
@ -23,7 +23,8 @@
*/
#include "precompiled.hpp"
#include "resolvedFieldEntry.hpp"
#include "cds/archiveBuilder.hpp"
#include "oops/resolvedFieldEntry.hpp"
void ResolvedFieldEntry::print_on(outputStream* st) const {
st->print_cr("Field Entry:");
@ -43,8 +44,14 @@ void ResolvedFieldEntry::print_on(outputStream* st) const {
st->print_cr(" - Put Bytecode: %s", Bytecodes::name((Bytecodes::Code)put_code()));
}
#if INCLUDE_CDS
void ResolvedFieldEntry::remove_unshareable_info() {
u2 saved_cpool_index = _cpool_index;
memset(this, 0, sizeof(*this));
_cpool_index = saved_cpool_index;
}
void ResolvedFieldEntry::mark_and_relocate() {
ArchiveBuilder::current()->mark_and_relocate_to_buffered_addr(&_field_holder);
}
#endif

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2024, 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
@ -55,6 +55,17 @@ class ResolvedFieldEntry {
u1 _flags; // Flags: [0000|00|is_final|is_volatile]
u1 _get_code, _put_code; // Get and Put bytecodes of the field
void copy_from(const ResolvedFieldEntry& other) {
_field_holder = other._field_holder;
_field_offset = other._field_offset;
_field_index = other._field_index;
_cpool_index = other._cpool_index;
_tos_state = other._tos_state;
_flags = other._flags;
_get_code = other._get_code;
_put_code = other._put_code;
}
public:
ResolvedFieldEntry(u2 cpi) :
_field_holder(nullptr),
@ -65,9 +76,19 @@ public:
_flags(0),
_get_code(0),
_put_code(0) {}
ResolvedFieldEntry() :
ResolvedFieldEntry(0) {}
ResolvedFieldEntry(const ResolvedFieldEntry& other) {
copy_from(other);
}
ResolvedFieldEntry& operator=(const ResolvedFieldEntry& other) {
copy_from(other);
return *this;
}
// Bit shift to get flags
// Note: Only two flags exists at the moment but more could be added
enum {
@ -131,7 +152,10 @@ public:
}
// CDS
#if INCLUDE_CDS
void remove_unshareable_info();
void mark_and_relocate();
#endif
// Offsets
static ByteSize field_holder_offset() { return byte_offset_of(ResolvedFieldEntry, _field_holder); }

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2024, 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
@ -23,6 +23,7 @@
*/
#include "precompiled.hpp"
#include "cds/archiveBuilder.hpp"
#include "code/compressedStream.hpp"
#include "oops/method.hpp"
#include "oops/resolvedIndyEntry.hpp"
@ -37,6 +38,7 @@ bool ResolvedIndyEntry::check_no_old_or_obsolete_entry() {
}
}
#if INCLUDE_CDS
void ResolvedIndyEntry::remove_unshareable_info() {
u2 saved_resolved_references_index = _resolved_references_index;
u2 saved_cpool_index = _cpool_index;
@ -45,6 +47,12 @@ void ResolvedIndyEntry::remove_unshareable_info() {
_cpool_index = saved_cpool_index;
}
void ResolvedIndyEntry::mark_and_relocate() {
assert(is_resolved(), "must be");
ArchiveBuilder::current()->mark_and_relocate_to_buffered_addr(&_method);
}
#endif
void ResolvedIndyEntry::print_on(outputStream* st) const {
st->print_cr("Resolved InvokeDynamic Info:");
if (_method != nullptr) {

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2024, 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
@ -129,7 +129,10 @@ public:
bool check_no_old_or_obsolete_entry();
// CDS
#if INCLUDE_CDS
void remove_unshareable_info();
void mark_and_relocate();
#endif
// Offsets
static ByteSize method_offset() { return byte_offset_of(ResolvedIndyEntry, _method); }

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2024, 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
@ -23,6 +23,7 @@
*/
#include "precompiled.hpp"
#include "cds/archiveBuilder.hpp"
#include "oops/method.hpp"
#include "oops/resolvedMethodEntry.hpp"
@ -50,10 +51,23 @@ void ResolvedMethodEntry::reset_entry() {
}
}
#if INCLUDE_CDS
void ResolvedMethodEntry::remove_unshareable_info() {
reset_entry();
}
void ResolvedMethodEntry::mark_and_relocate(ConstantPool* src_cp) {
if (_method == nullptr) {
assert(bytecode2() == Bytecodes::_invokevirtual, "");
} else {
ArchiveBuilder::current()->mark_and_relocate_to_buffered_addr(&_method);
}
if (bytecode1() == Bytecodes::_invokeinterface) {
ArchiveBuilder::current()->mark_and_relocate_to_buffered_addr(&_entry_specific._interface_klass);
}
}
#endif
void ResolvedMethodEntry::print_on(outputStream* st) const {
st->print_cr("Method Entry:");

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2024, 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
@ -224,7 +224,10 @@ class ResolvedMethodEntry {
void reset_entry();
// CDS
#if INCLUDE_CDS
void remove_unshareable_info();
void mark_and_relocate(ConstantPool* src_cp);
#endif
// Offsets
static ByteSize klass_offset() { return byte_offset_of(ResolvedMethodEntry, _entry_specific._interface_klass); }

@ -3729,7 +3729,7 @@ JVM_ENTRY(void, JVM_LogLambdaFormInvoker(JNIEnv *env, jstring line))
}
if (ClassListWriter::is_enabled()) {
ClassListWriter w;
w.stream()->print_cr("%s %s", LAMBDA_FORM_TAG, c_line);
w.stream()->print_cr("%s %s", ClassListParser::lambda_form_tag(), c_line);
}
}
#endif // INCLUDE_CDS

@ -24,6 +24,7 @@
#include "precompiled.hpp"
#include "cds/cds_globals.hpp"
#include "cds/classListWriter.hpp"
#include "cds/dynamicArchive.hpp"
#include "classfile/classLoader.hpp"
#include "classfile/classLoaderDataGraph.hpp"
@ -433,6 +434,10 @@ void before_exit(JavaThread* thread, bool halt) {
}
#endif
#if INCLUDE_CDS
ClassListWriter::write_resolved_constants();
#endif
// Hang forever on exit if we're reporting an error.
if (ShowMessageBoxOnError && VMError::is_error_reported()) {
os::infinite_sleep();

@ -448,6 +448,7 @@ hotspot_appcds_dynamic = \
-runtime/cds/appcds/lambdaForm/DefaultClassListLFInvokers.java \
-runtime/cds/appcds/methodHandles \
-runtime/cds/appcds/sharedStrings \
-runtime/cds/appcds/resolvedConstants \
-runtime/cds/appcds/ArchiveRelocationTest.java \
-runtime/cds/appcds/BadBSM.java \
-runtime/cds/appcds/DumpClassList.java \

@ -0,0 +1,128 @@
/*
* Copyright (c) 2024, 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 Dump time resolutiom of constant pool entries.
* @requires vm.cds
* @requires vm.compMode != "Xcomp"
* @library /test/lib
* @build ResolvedConstants
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar ResolvedConstantsApp ResolvedConstantsFoo ResolvedConstantsBar
* @run driver ResolvedConstants
*/
import jdk.test.lib.cds.CDSOptions;
import jdk.test.lib.cds.CDSTestUtils;
import jdk.test.lib.helpers.ClassFileInstaller;
public class ResolvedConstants {
static final String classList = "ResolvedConstants.classlist";
static final String appJar = ClassFileInstaller.getJarPath("app.jar");
static final String mainClass = ResolvedConstantsApp.class.getName();
public static void main(String[] args) throws Exception {
// dump class list
CDSTestUtils.dumpClassList(classList, "-cp", appJar, mainClass)
.assertNormalExit(output -> {
output.shouldContain("Hello ResolvedConstantsApp");
});
CDSOptions opts = (new CDSOptions())
.addPrefix("-XX:ExtraSharedClassListFile=" + classList,
"-cp", appJar,
"-Xlog:cds+resolve=trace");
CDSTestUtils.createArchiveAndCheck(opts)
// Always resolve reference when a class references itself
.shouldMatch("cds,resolve.*archived klass.* ResolvedConstantsApp app => ResolvedConstantsApp app")
// Always resolve reference when a class references a super class
.shouldMatch("cds,resolve.*archived klass.* ResolvedConstantsApp app => java/lang/Object boot")
.shouldMatch("cds,resolve.*archived klass.* ResolvedConstantsBar app => ResolvedConstantsFoo app")
// Always resolve reference when a class references a super interface
.shouldMatch("cds,resolve.*archived klass.* ResolvedConstantsApp app => java/lang/Runnable boot")
// java/lang/System is in the root loader but ResolvedConstantsApp is loaded by the app loader.
// Even though System is in the vmClasses list, when ResolvedConstantsApp looks up
// "java/lang/System" in its ConstantPool, the app loader may not have resolved the System
// class yet (i.e., there's no initiaited class entry for System in the app loader's dictionary)
.shouldMatch("cds,resolve.*reverted klass.* ResolvedConstantsApp .*java/lang/System")
// Always resolve references to fields in the current class or super class(es)
.shouldMatch("cds,resolve.*archived field.* ResolvedConstantsBar => ResolvedConstantsBar.b:I")
.shouldMatch("cds,resolve.*archived field.* ResolvedConstantsBar => ResolvedConstantsBar.a:I")
.shouldMatch("cds,resolve.*archived field.* ResolvedConstantsBar => ResolvedConstantsFoo.a:I")
// Do not resolve field references to child classes
.shouldMatch("cds,resolve.*archived field.* ResolvedConstantsFoo => ResolvedConstantsFoo.a:I")
.shouldMatch("cds,resolve.*reverted field.* ResolvedConstantsFoo ResolvedConstantsBar.a:I")
.shouldMatch("cds,resolve.*reverted field.* ResolvedConstantsFoo ResolvedConstantsBar.b:I")
// Do not resolve field references to unrelated classes
.shouldMatch("cds,resolve.*reverted field.* ResolvedConstantsApp ResolvedConstantsBar.a:I")
.shouldMatch("cds,resolve.*reverted field.* ResolvedConstantsApp ResolvedConstantsBar.b:I")
;
}
}
class ResolvedConstantsApp implements Runnable {
public static void main(String args[]) {
System.out.println("Hello ResolvedConstantsApp");
Object a = new ResolvedConstantsApp();
((Runnable)a).run();
ResolvedConstantsFoo foo = new ResolvedConstantsFoo();
ResolvedConstantsBar bar = new ResolvedConstantsBar();
bar.a ++;
bar.b ++;
bar.doit();
}
public void run() {}
}
class ResolvedConstantsFoo {
int a = 1;
void doit() {
}
void doBar(ResolvedConstantsBar bar) {
bar.a ++;
bar.b ++;
}
}
class ResolvedConstantsBar extends ResolvedConstantsFoo {
int b = 2;
void doit() {
System.out.println("Hello ResolvedConstantsBar and " + ResolvedConstantsFoo.class.getName());
System.out.println("a = " + a);
System.out.println("a = " + ((ResolvedConstantsFoo)this).a);
System.out.println("b = " + b);
doBar(this);
}
}

@ -0,0 +1,72 @@
/*
* Copyright (c) 2024, 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 Fieldref entry for putfield bytecodes for a final field cannot be preresolved if it's used by a
* method outside of <clinit>
* @requires vm.cds
* @requires vm.compMode != "Xcomp"
* @library /test/lib
* @build ResolvedPutFieldHelper
* @build ResolvedPutField
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar ResolvedPutFieldApp ResolvedPutFieldHelper
* @run driver ResolvedPutField
*/
import jdk.test.lib.cds.CDSOptions;
import jdk.test.lib.cds.CDSTestUtils;
import jdk.test.lib.helpers.ClassFileInstaller;
public class ResolvedPutField {
static final String classList = "ResolvedPutField.classlist";
static final String appJar = ClassFileInstaller.getJarPath("app.jar");
static final String mainClass = ResolvedPutFieldApp.class.getName();
static final String error = "Update to non-static final field ResolvedPutFieldHelper.x attempted from a different method (set_x) than the initializer method <init>";
public static void main(String[] args) throws Exception {
// dump class list
CDSTestUtils.dumpClassList(classList, "-cp", appJar, mainClass)
.assertNormalExit(error);
CDSOptions opts = (new CDSOptions())
.addPrefix("-XX:ExtraSharedClassListFile=" + classList,
"-cp", appJar,
"-Xlog:cds+resolve=trace");
CDSTestUtils.createArchiveAndCheck(opts)
.shouldMatch("cds,resolve.*Failed to resolve putfield .*ResolvedPutFieldHelper -> ResolvedPutFieldHelper.x:I");
}
}
class ResolvedPutFieldApp {
public static void main(String args[]) {
try {
ResolvedPutFieldHelper.main(args);
} catch (IllegalAccessError e) {
System.out.println("IllegalAccessError expected:");
System.out.println(e);
System.exit(0);
}
throw new RuntimeException("IllegalAccessError expected!");
}
}

@ -0,0 +1,89 @@
/*
* Copyright (c) 2024, 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.
*
*/
/*
class ResolvedPutFieldHelper {
int x; // change it to 'final'
ResolvedPutFieldHelper() { x = 1; }
void set_x() { x = 2; }
public static void main(String args[]) {
ResolvedPutFieldHelper s = new ResolvedPutFieldHelper();
s.set_x();
System.out.println(s.x);
}
}
*/
super class ResolvedPutFieldHelper
version 66:0
{
//WAS Field x:I;
final Field x:I;
Method "<init>":"()V"
stack 2 locals 1
{
aload_0;
invokespecial Method java/lang/Object."<init>":"()V";
aload_0;
iconst_1;
putfield Field x:"I";
return;
}
// set_x is not allowed to write to the final "x" field. If CDS pre-resolves its
// ResolvedFieldEntry for the putfield bytecode, then we cannot get
// the IllegalAccessError at runtime. See JDK-8157181 for the code that
// throws the IllegalAccessError.
Method set_x:"()V"
stack 2 locals 1
{
aload_0;
iconst_2;
putfield Field x:"I";
return;
}
public static Method main:"([Ljava/lang/String;)V"
stack 2 locals 2
{
new class ResolvedPutFieldHelper;
dup;
invokespecial Method "<init>":"()V";
astore_1;
aload_1;
invokevirtual Method set_x:"()V";
getstatic Field java/lang/System.out:"Ljava/io/PrintStream;";
aload_1;
getfield Field x:"I";
invokevirtual Method java/io/PrintStream.println:"(I)V";
return;
}
}