8293980: Resolve CONSTANT_FieldRef at CDS dump time
Reviewed-by: erikj, matsaave, heidinga
This commit is contained in:
parent
eb2488fd17
commit
b818679eba
make
src/hotspot/share
cds
archiveBuilder.cpparchiveBuilder.hpparchiveUtils.cppclassListParser.cppclassListParser.hppclassListWriter.cppclassListWriter.hppclassPrelinker.cppclassPrelinker.hppdumpAllocStats.cppdumpAllocStats.hpplambdaFormInvokers.hpplambdaFormInvokers.inline.hpp
interpreter
oops
constantPool.cppconstantPool.hppcpCache.cppcpCache.hppinstanceKlass.cppresolvedFieldEntry.cppresolvedFieldEntry.hppresolvedIndyEntry.cppresolvedIndyEntry.hppresolvedMethodEntry.cppresolvedMethodEntry.hpp
prims
runtime
test/hotspot/jtreg
TEST.groups
runtime/cds/appcds/resolvedConstants
@ -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
|
||||
|
38
src/hotspot/share/cds/lambdaFormInvokers.inline.hpp
Normal file
38
src/hotspot/share/cds/lambdaFormInvokers.inline.hpp
Normal file
@ -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;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user