diff --git a/make/RunTests.gmk b/make/RunTests.gmk index 747d8f84dbc..636d1ed18b2 100644 --- a/make/RunTests.gmk +++ b/make/RunTests.gmk @@ -45,7 +45,7 @@ ifneq ($(TEST_VM_OPTS), ) endif $(eval $(call ParseKeywordVariable, TEST_OPTS, \ - SINGLE_KEYWORDS := JOBS TIMEOUT_FACTOR JCOV JCOV_DIFF_CHANGESET, \ + SINGLE_KEYWORDS := JOBS TIMEOUT_FACTOR JCOV JCOV_DIFF_CHANGESET AOT_JDK, \ STRING_KEYWORDS := VM_OPTIONS JAVA_OPTIONS, \ )) @@ -202,11 +202,12 @@ $(eval $(call SetTestOpt,JOBS,JTREG)) $(eval $(call SetTestOpt,TIMEOUT_FACTOR,JTREG)) $(eval $(call SetTestOpt,FAILURE_HANDLER_TIMEOUT,JTREG)) $(eval $(call SetTestOpt,REPORT,JTREG)) +$(eval $(call SetTestOpt,AOT_JDK,JTREG)) $(eval $(call ParseKeywordVariable, JTREG, \ SINGLE_KEYWORDS := JOBS TIMEOUT_FACTOR FAILURE_HANDLER_TIMEOUT \ TEST_MODE ASSERT VERBOSE RETAIN TEST_THREAD_FACTORY MAX_MEM RUN_PROBLEM_LISTS \ - RETRY_COUNT REPEAT_COUNT MAX_OUTPUT REPORT $(CUSTOM_JTREG_SINGLE_KEYWORDS), \ + RETRY_COUNT REPEAT_COUNT MAX_OUTPUT REPORT AOT_JDK $(CUSTOM_JTREG_SINGLE_KEYWORDS), \ STRING_KEYWORDS := OPTIONS JAVA_OPTIONS VM_OPTIONS KEYWORDS \ EXTRA_PROBLEM_LISTS LAUNCHER_OPTIONS \ $(CUSTOM_JTREG_STRING_KEYWORDS), \ @@ -702,6 +703,58 @@ define SetJtregValue endif endef + +# Parameter 1 is the name of the rule. +# +# Remaining parameters are named arguments. +# VM_OPTIONS List of JVM arguments to use when creating AOT cache +# +# After calling this, the following variables are defined +# $1_AOT_TARGETS List of all targets that the test rule will need to depend on +# $1_AOT_JDK_CACHE The AOT cache file to be used to run the test with +# +SetupAot = $(NamedParamsMacroTemplate) +define SetupAotBody + $1_AOT_JDK_CONF := $$($1_TEST_SUPPORT_DIR)/aot/jdk.aotconf + $1_AOT_JDK_CACHE := $$($1_TEST_SUPPORT_DIR)/aot/jdk.aotcache + + $1_JAVA_TOOL_OPTS := $$(addprefix -J, $$($1_VM_OPTIONS)) + + $$($1_AOT_JDK_CACHE): $$(JDK_IMAGE_DIR)/release + $$(call MakeDir, $$($1_TEST_SUPPORT_DIR)/aot) + + $(foreach jtool, javac javap jlink jar, \ + $(info AOT: Create cache configuration for $(jtool)) \ + $$(call ExecuteWithLog, $$($1_TEST_SUPPORT_DIR)/aot.$(jtool), ( \ + $$(FIXPATH) $(JDK_UNDER_TEST)/bin/$(jtool) $$($1_JAVA_TOOL_OPTS) \ + -J-XX:AOTMode=record -J-XX:AOTConfiguration=$$($1_AOT_JDK_CONF).$(jtool) --help \ + )) + ) + + $$(info AOT: Copy $(JDK_UNDER_TEST)/lib/classlist to $$($1_AOT_JDK_CONF).jdk ) + $$(call ExecuteWithLog, $$($1_TEST_SUPPORT_DIR)/aot, ( \ + $$(FIXPATH) $(CP) $(JDK_UNDER_TEST)/lib/classlist $$($1_AOT_JDK_CONF).jdk \ + )) + + $$(FIXPATH) $$(CAT) $$($1_AOT_JDK_CONF).* > $$($1_AOT_JDK_CONF).temp + $$(FIXPATH) $$(CAT) $$($1_AOT_JDK_CONF).temp | $(GREP) -v '#' | $(GREP) -v '@' | $(SORT) | \ + $(SED) -e 's/id:.*//g' | uniq \ + > $$($1_AOT_JDK_CONF) + $$(FIXPATH) $$(CAT) $$($1_AOT_JDK_CONF).temp | $(GREP) '@cp' | $(SORT) \ + >> $$($1_AOT_JDK_CONF) + + $$(info AOT: Generate AOT cache $$($1_AOT_JDK_CACHE) with flags: $$($1_VM_OPTIONS)) + $$(call ExecuteWithLog, $$($1_TEST_SUPPORT_DIR)/aot, ( \ + $$(FIXPATH) $(JDK_UNDER_TEST)/bin/java \ + $$($1_VM_OPTIONS) -Xlog:cds,cds+class=debug:file=$$($1_AOT_JDK_CACHE).log \ + -XX:AOTMode=create -XX:AOTConfiguration=$$($1_AOT_JDK_CONF) -XX:AOTCache=$$($1_AOT_JDK_CACHE) \ + )) + + $1_AOT_TARGETS += $$($1_AOT_JDK_CACHE) + +endef + + SetupRunJtregTest = $(NamedParamsMacroTemplate) define SetupRunJtregTestBody $1_TEST_RESULTS_DIR := $$(TEST_RESULTS_DIR)/$1 @@ -762,6 +815,7 @@ define SetupRunJtregTestBody JTREG_RETRY_COUNT ?= 0 JTREG_REPEAT_COUNT ?= 0 JTREG_REPORT ?= files + JTREG_AOT_JDK ?= false ifneq ($$(JTREG_RETRY_COUNT), 0) ifneq ($$(JTREG_REPEAT_COUNT), 0) @@ -891,6 +945,17 @@ define SetupRunJtregTestBody endif endif + ifeq ($$(JTREG_AOT_JDK), true) + $$(info Add AOT target for $1) + $$(eval $$(call SetupAot, $1, VM_OPTIONS := $$(JTREG_ALL_OPTIONS) )) + + $$(info AOT_TARGETS=$$($1_AOT_TARGETS)) + $$(info AOT_JDK_CACHE=$$($1_AOT_JDK_CACHE)) + + $1_JTREG_BASIC_OPTIONS += -vmoption:-XX:AOTCache="$$($1_AOT_JDK_CACHE)" + endif + + $$(eval $$(call SetupRunJtregTestCustom, $1)) # SetupRunJtregTestCustom might also adjust JTREG_AUTO_ variables @@ -906,6 +971,7 @@ define SetupRunJtregTestBody JTREG_TIMEOUT_FACTOR ?= $$(JTREG_AUTO_TIMEOUT_FACTOR) clean-outputdirs-$1: + $$(call LogWarn, Clean up dirs for $1) $$(RM) -r $$($1_TEST_SUPPORT_DIR) $$(RM) -r $$($1_TEST_RESULTS_DIR) @@ -953,7 +1019,7 @@ define SetupRunJtregTestBody done endif - run-test-$1: pre-run-test clean-outputdirs-$1 + run-test-$1: clean-outputdirs-$1 pre-run-test $$($1_AOT_TARGETS) $$(call LogWarn) $$(call LogWarn, Running test '$$($1_TEST)') $$(call MakeDir, $$($1_TEST_RESULTS_DIR) $$($1_TEST_SUPPORT_DIR) \ diff --git a/src/hotspot/cpu/aarch64/aarch64.ad b/src/hotspot/cpu/aarch64/aarch64.ad index a97d01b7683..1015b631643 100644 --- a/src/hotspot/cpu/aarch64/aarch64.ad +++ b/src/hotspot/cpu/aarch64/aarch64.ad @@ -5755,10 +5755,6 @@ opclass memory(indirect, indIndexScaled, indIndexScaledI2L, indIndexI2L, indInde indirectN, indIndexScaledN, indIndexScaledI2LN, indIndexI2LN, indIndexN, indOffIN, indOffLN, indirectX2P, indOffX2P); -opclass memory_noindex(indirect, - indOffI1, indOffL1,indOffI2, indOffL2, indOffI4, indOffL4, indOffI8, indOffL8, - indirectN, indOffIN, indOffLN, indirectX2P, indOffX2P); - // iRegIorL2I is used for src inputs in rules for 32 bit int (I) // operations. it allows the src to be either an iRegI or a (ConvL2I // iRegL). in the latter case the l2i normally planted for a ConvL2I @@ -6695,16 +6691,21 @@ instruct loadNKlass(iRegNNoSp dst, memory4 mem) ins_pipe(iload_reg_mem); %} -instruct loadNKlassCompactHeaders(iRegNNoSp dst, memory_noindex mem) +instruct loadNKlassCompactHeaders(iRegNNoSp dst, memory4 mem) %{ match(Set dst (LoadNKlass mem)); predicate(!needs_acquiring_load(n) && UseCompactObjectHeaders); ins_cost(4 * INSN_COST); - format %{ "load_narrow_klass_compact $dst, $mem\t# compressed class ptr" %} + format %{ + "ldrw $dst, $mem\t# compressed class ptr, shifted\n\t" + "lsrw $dst, $dst, markWord::klass_shift_at_offset" + %} ins_encode %{ - assert($mem$$index$$Register == noreg, "must not have indexed address"); - __ load_narrow_klass_compact_c2($dst$$Register, $mem$$base$$Register, $mem$$disp); + // inlined aarch64_enc_ldrw + loadStore(masm, &MacroAssembler::ldrw, $dst$$Register, $mem->opcode(), + as_Register($mem$$base), $mem$$index, $mem$$scale, $mem$$disp, 4); + __ lsrw($dst$$Register, $dst$$Register, markWord::klass_shift_at_offset); %} ins_pipe(iload_reg_mem); %} diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp index 4c22133c056..3b0c8ae432c 100644 --- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp @@ -2690,12 +2690,3 @@ bool C2_MacroAssembler::in_scratch_emit_size() { } return MacroAssembler::in_scratch_emit_size(); } - -void C2_MacroAssembler::load_narrow_klass_compact_c2(Register dst, Register obj, int disp) { - // Note: Don't clobber obj anywhere in that method! - - // The incoming address is pointing into obj-start + klass_offset_in_bytes. We need to extract - // obj-start, so that we can load from the object's mark-word instead. - ldr(dst, Address(obj, disp - oopDesc::klass_offset_in_bytes())); - lsr(dst, dst, markWord::klass_shift); -} diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp index c6ddcf46cba..d61b050407d 100644 --- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp @@ -186,6 +186,4 @@ void vector_signum_sve(FloatRegister dst, FloatRegister src, FloatRegister zero, FloatRegister one, FloatRegister vtmp, PRegister pgtmp, SIMD_RegVariant T); - void load_narrow_klass_compact_c2(Register dst, Register obj, int disp); - #endif // CPU_AARCH64_C2_MACROASSEMBLER_AARCH64_HPP diff --git a/src/hotspot/cpu/riscv/stubRoutines_riscv.cpp b/src/hotspot/cpu/riscv/stubRoutines_riscv.cpp index 6d5492b86b3..28b797a639e 100644 --- a/src/hotspot/cpu/riscv/stubRoutines_riscv.cpp +++ b/src/hotspot/cpu/riscv/stubRoutines_riscv.cpp @@ -34,16 +34,6 @@ // Implementation of the platform-specific part of StubRoutines - for // a description of how to extend it, see the stubRoutines.hpp file. -address StubRoutines::riscv::_get_previous_sp_entry = nullptr; - -address StubRoutines::riscv::_f2i_fixup = nullptr; -address StubRoutines::riscv::_f2l_fixup = nullptr; -address StubRoutines::riscv::_d2i_fixup = nullptr; -address StubRoutines::riscv::_d2l_fixup = nullptr; -address StubRoutines::riscv::_float_sign_mask = nullptr; -address StubRoutines::riscv::_float_sign_flip = nullptr; -address StubRoutines::riscv::_double_sign_mask = nullptr; -address StubRoutines::riscv::_double_sign_flip = nullptr; address StubRoutines::riscv::_zero_blocks = nullptr; address StubRoutines::riscv::_compare_long_string_LL = nullptr; address StubRoutines::riscv::_compare_long_string_UU = nullptr; @@ -52,7 +42,6 @@ address StubRoutines::riscv::_compare_long_string_UL = nullptr; address StubRoutines::riscv::_string_indexof_linear_ll = nullptr; address StubRoutines::riscv::_string_indexof_linear_uu = nullptr; address StubRoutines::riscv::_string_indexof_linear_ul = nullptr; -address StubRoutines::riscv::_large_byte_array_inflate = nullptr; bool StubRoutines::riscv::_completed = false; diff --git a/src/hotspot/cpu/riscv/stubRoutines_riscv.hpp b/src/hotspot/cpu/riscv/stubRoutines_riscv.hpp index 3d1f4a61f00..a099d71475e 100644 --- a/src/hotspot/cpu/riscv/stubRoutines_riscv.hpp +++ b/src/hotspot/cpu/riscv/stubRoutines_riscv.hpp @@ -47,18 +47,6 @@ class riscv { friend class StubGenerator; private: - static address _get_previous_sp_entry; - - static address _f2i_fixup; - static address _f2l_fixup; - static address _d2i_fixup; - static address _d2l_fixup; - - static address _float_sign_mask; - static address _float_sign_flip; - static address _double_sign_mask; - static address _double_sign_flip; - static address _zero_blocks; static address _compare_long_string_LL; @@ -68,48 +56,11 @@ class riscv { static address _string_indexof_linear_ll; static address _string_indexof_linear_uu; static address _string_indexof_linear_ul; - static address _large_byte_array_inflate; static bool _completed; public: - static address get_previous_sp_entry() { - return _get_previous_sp_entry; - } - - static address f2i_fixup() { - return _f2i_fixup; - } - - static address f2l_fixup() { - return _f2l_fixup; - } - - static address d2i_fixup() { - return _d2i_fixup; - } - - static address d2l_fixup() { - return _d2l_fixup; - } - - static address float_sign_mask() { - return _float_sign_mask; - } - - static address float_sign_flip() { - return _float_sign_flip; - } - - static address double_sign_mask() { - return _double_sign_mask; - } - - static address double_sign_flip() { - return _double_sign_flip; - } - static address zero_blocks() { return _zero_blocks; } @@ -142,10 +93,6 @@ class riscv { return _string_indexof_linear_uu; } - static address large_byte_array_inflate() { - return _large_byte_array_inflate; - } - static bool complete() { return _completed; } diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp index 06d93ddea26..8d0af29e91d 100644 --- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp @@ -7073,13 +7073,3 @@ void C2_MacroAssembler::vector_saturating_op(int ideal_opc, BasicType elem_bt, X vector_saturating_op(ideal_opc, elem_bt, dst, src1, src2, vlen_enc); } } - -#ifdef _LP64 -void C2_MacroAssembler::load_narrow_klass_compact_c2(Register dst, Address src) { - // The incoming address is pointing into obj-start + klass_offset_in_bytes. We need to extract - // obj-start, so that we can load from the object's mark-word instead. Usually the address - // comes as obj-start in obj and klass_offset_in_bytes in disp. - movq(dst, src.plus_disp(-oopDesc::klass_offset_in_bytes())); - shrq(dst, markWord::klass_shift); -} -#endif diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp index 523200486cc..3a36fd75e3f 100644 --- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp @@ -583,8 +583,4 @@ public: void select_from_two_vectors_evex(BasicType elem_bt, XMMRegister dst, XMMRegister src1, XMMRegister src2, int vlen_enc); -#ifdef _LP64 - void load_narrow_klass_compact_c2(Register dst, Address src); -#endif - #endif // CPU_X86_C2_MACROASSEMBLER_X86_HPP diff --git a/src/hotspot/cpu/x86/x86_64.ad b/src/hotspot/cpu/x86/x86_64.ad index fc083ecfa24..550c8047034 100644 --- a/src/hotspot/cpu/x86/x86_64.ad +++ b/src/hotspot/cpu/x86/x86_64.ad @@ -4368,11 +4368,15 @@ instruct loadNKlassCompactHeaders(rRegN dst, memory mem, rFlagsReg cr) match(Set dst (LoadNKlass mem)); effect(KILL cr); ins_cost(125); // XXX - format %{ "load_narrow_klass_compact $dst, $mem\t# compressed klass ptr" %} - ins_encode %{ - __ load_narrow_klass_compact_c2($dst$$Register, $mem$$Address); + format %{ + "movl $dst, $mem\t# compressed klass ptr, shifted\n\t" + "shrl $dst, markWord::klass_shift_at_offset" %} - ins_pipe(pipe_slow); // XXX + ins_encode %{ + __ movl($dst$$Register, $mem$$Address); + __ shrl($dst$$Register, markWord::klass_shift_at_offset); + %} + ins_pipe(ialu_reg_mem); // XXX %} // Load Float diff --git a/src/hotspot/share/cds/aotClassInitializer.cpp b/src/hotspot/share/cds/aotClassInitializer.cpp new file mode 100644 index 00000000000..b09dfcde6b1 --- /dev/null +++ b/src/hotspot/share/cds/aotClassInitializer.cpp @@ -0,0 +1,359 @@ +/* + * 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. + * + */ + +#include "precompiled.hpp" +#include "cds/aotClassInitializer.hpp" +#include "cds/archiveBuilder.hpp" +#include "cds/cdsConfig.hpp" +#include "cds/heapShared.hpp" +#include "classfile/vmSymbols.hpp" +#include "oops/instanceKlass.inline.hpp" +#include "oops/symbol.hpp" +#include "runtime/javaCalls.hpp" + +// Detector for class names we wish to handle specially. +// It is either an exact string match or a string prefix match. +class AOTClassInitializer::AllowedSpec { + const char* _class_name; + bool _is_prefix; + int _len; +public: + AllowedSpec(const char* class_name, bool is_prefix = false) + : _class_name(class_name), _is_prefix(is_prefix) + { + _len = (class_name == nullptr) ? 0 : (int)strlen(class_name); + } + const char* class_name() { return _class_name; } + + bool matches(Symbol* name, int len) { + assert(_class_name != nullptr, "caller resp."); + if (_is_prefix) { + return len >= _len && name->starts_with(_class_name); + } else { + return len == _len && name->equals(_class_name); + } + } +}; + + +// Tell if ik has a name that matches one of the given specs. +bool AOTClassInitializer::is_allowed(AllowedSpec* specs, InstanceKlass* ik) { + Symbol* name = ik->name(); + int len = name->utf8_length(); + for (AllowedSpec* s = specs; s->class_name() != nullptr; s++) { + if (s->matches(name, len)) { + // If a type is included in the tables inside can_archive_initialized_mirror(), we require that + // - all super classes must be included + // - all super interfaces that have must be included. + // This ensures that in the production run, we don't run the of a supertype but skips + // ik's . + if (ik->java_super() != nullptr) { + DEBUG_ONLY(ResourceMark rm); + assert(AOTClassInitializer::can_archive_initialized_mirror(ik->java_super()), + "super class %s of %s must be aot-initialized", ik->java_super()->external_name(), + ik->external_name()); + } + + Array* interfaces = ik->local_interfaces(); + int len = interfaces->length(); + for (int i = 0; i < len; i++) { + InstanceKlass* intf = interfaces->at(i); + if (intf->class_initializer() != nullptr) { + assert(AOTClassInitializer::can_archive_initialized_mirror(intf), + "super interface %s (which has ) of %s must be aot-initialized", intf->external_name(), + ik->external_name()); + } + } + + return true; + } + } + return false; +} + + +bool AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass* ik) { + assert(!ArchiveBuilder::current()->is_in_buffer_space(ik), "must be source klass"); + if (!CDSConfig::is_initing_classes_at_dump_time()) { + return false; + } + + if (!ik->is_initialized()) { + return false; + } + + if (ik->is_hidden()) { + return HeapShared::is_archivable_hidden_klass(ik); + } + + if (ik->is_enum_subclass()) { + return true; + } + + // About "static field that may hold a different value" errors: + // + // Automatic selection for aot-inited classes + // ========================================== + // + // When CDSConfig::is_initing_classes_at_dump_time() is enabled, + // HeapShared::find_all_aot_initialized_classes() finds the classes of all + // heap objects that are reachable from HeapShared::_run_time_special_subgraph, + // and mark these classes as aot-inited. This preserves the initialized + // mirrors of these classes, and their methods are NOT executed + // at runtime. + // + // For example, with -XX:+AOTInvokeDynamicLinking, _run_time_special_subgraph + // will contain some DirectMethodHandle objects. As a result, the DirectMethodHandle + // class is automatically marked as aot-inited. + // + // When a class is aot-inited, its static fields are already set up + // by executing the method at AOT assembly time. Later on + // in the production run, when the class would normally be + // initialized, the VM performs guarding and synchronization as if + // it were going to run the again, but instead it simply + // observes that that class was aot-inited. The VM assumes that, if + // it were to run again, it would get a semantically + // equivalent set of final field values, so it just adopts the + // existing field values (from AOT assembly) and skips the call to + // . There may at that point be fixups performed by ad hoc + // code, if the VM recognizes a request in the library. + // + // It is true that this is not generally correct for all possible + // Java code. A method might have a side effect beyond + // initializing the static fields. It might send an email somewhere + // noting the current time of day. In that case, such an email + // would have been sent during the AOT assembly phase, and the email + // would NOT be sent again during production. This is clearly NOT + // what a user would want, if this were a general purpose facility. + // But in fact it is only for certain well-behaved classes, which + // are known NOT to have such side effects. We know this because + // the optimization (of skipping for aot-init classes) is + // only applied to classes fully defined by the JDK. + // + // (A day may come when we figure out how to gracefully extend this + // optimization to untrusted third parties, but it is not this day.) + // + // Manual selection + // ================ + // + // There are important cases where one aot-init class has a side + // effect on another aot-class, a side effect which is not captured + // in any static field value in either class. The simplest example + // is class A forces the initialization of class B. In that case, + // we need to aot-init either both classes or neither. From looking + // at the JDK state after AOT assembly is done, it is hard to tell + // that A "touched" B and B might escape our notice. Another common + // example is A copying a field value from B. We don't know where A + // got the value, but it would be wrong to re-initialize B at + // startup, while keeping the snapshot of the old B value in A. In + // general, if we aot-init A, we need to aot-init every class B that + // somehow contributed to A's initial state, and every class C that + // was somehow side-effected by A's initialization. We say that the + // aot-init of A is "init-coupled" to those of B and C. + // + // So there are init-coupled classes that cannot be automatically discovered. For + // example, DirectMethodHandle::IMPL_NAMES points to MethodHandles::IMPL_NAMES, + // but the MethodHandles class is not automatically marked because there are + // no archived instances of the MethodHandles type. + // + // If we aot-initialize DirectMethodHandle, but allow MethodHandles to be + // initialized at runtime, MethodHandles::IMPL_NAMES will get a different + // value than DirectMethodHandle::IMPL_NAMES. This *may or may not* be a problem, + // but to ensure compatibility, we should try to preserve the identity equality + // of these two fields. + // + // To do that, we add MethodHandles to the indy_specs[] table below. + // + // Luckily we do not need to be all-knowing in order to choose which + // items to add to that table. We have tools to help detect couplings. + // + // Automatic validation + // ==================== + // + // CDSHeapVerifier is used to detect potential problems with identity equality. + // + // A class B is assumed to be init-coupled to some aot-init class if + // B has a field which points to a live object X in the AOT heap. + // The live object X was created by some other class A which somehow + // used B's reference to X, perhaps with the help of an intermediate + // class Z. Or, B pulled the reference to X from some other class + // Y, and B obtained that reference from Y (or an intermediate Z). + // It is not certain how X got into the heap, nor whether B + // contributed it, but it is a good heuristic that B is init-coupled + // to X's class or some other aot-init class. In any case, B should + // be made an aot-init class as well, unless a manual inspection + // shows that would be a problem. If there is a problem, then the + // JDK code for B and/or X probably needs refactoring. If there is + // no problem, we add B to the list. Typically the same scan will + // find any other accomplices Y, Z, etc. One failure would be a + // class Q whose only initialization action is to scribble a special + // value into B, from which the value X is derived and then makes + // its way into the heap. In that case, the heuristic does not + // identify Q. It is (currently) a human responsibility, of JDK + // engineers, not to write such dirty JDK code, or to repair it if + // it crops up. Eventually we may have tools, or even a user mode + // with design rules and checks, that will vet our code base more + // automatically. + // + // To see how the tool detects the problem with MethodHandles::IMPL_NAMES: + // + // - Comment out all the lines in indy_specs[] except the {nullptr} line. + // - Rebuild the JDK + // + // Then run the following: + // java -XX:AOTMode=record -XX:AOTConfiguration=jc.aotconfig com.sun.tools.javac.Main + // java -XX:AOTMode=create -Xlog:cds -XX:AOTCache=jc.aot -XX:AOTConfiguration=jc.aotconfig + // + // You will see an error like this: + // + // Archive heap points to a static field that may hold a different value at runtime: + // Field: java/lang/invoke/MethodHandles::IMPL_NAMES + // Value: java.lang.invoke.MemberName$Factory + // {0x000000060e906ae8} - klass: 'java/lang/invoke/MemberName$Factory' - flags: + // + // - ---- fields (total size 2 words): + // --- trace begin --- + // [ 0] {0x000000060e8deeb0} java.lang.Class (java.lang.invoke.DirectMethodHandle::IMPL_NAMES) + // [ 1] {0x000000060e906ae8} java.lang.invoke.MemberName$Factory + // --- trace end --- + // + // Trouble-shooting + // ================ + // + // If you see a "static field that may hold a different value" error, it's probably + // because you've made some changes in the JDK core libraries (most likely + // java.lang.invoke). + // + // - Did you add a new static field to a class that could be referenced by + // cached object instances of MethodType, MethodHandle, etc? You may need + // to add that class to indy_specs[]. + // - Did you modify the of the classes in java.lang.invoke such that + // a static field now points to an object that should not be cached (e.g., + // a native resource such as a file descriptior, or a Thread)? + // + // Note that these potential problems only occur when one class gets + // the aot-init treatment, AND another class is init-coupled to it, + // AND the coupling is not detected. Currently there are a number + // classes that get the aot-init treatment, in java.lang.invoke + // because of invokedynamic. They are few enough for now to be + // manually tracked. There may be more in the future. + + // IS_PREFIX means that we match all class names that start with a + // prefix. Otherwise, it is an exact match, of just one class name. + const bool IS_PREFIX = true; + + { + static AllowedSpec specs[] = { + // everybody's favorite super + {"java/lang/Object"}, + + // above we selected all enums; we must include their super as well + {"java/lang/Enum"}, + {nullptr} + }; + if (is_allowed(specs, ik)) { + return true; + } + } + + if (CDSConfig::is_dumping_invokedynamic()) { + // This table was created with the help of CDSHeapVerifier. + // Also, some $Holder classes are needed. E.g., Invokers. explicitly + // initializes Invokers$Holder. Since Invokers. won't be executed + // at runtime, we need to make sure Invokers$Holder is also aot-inited. + // + // We hope we can reduce the size of this list over time, and move + // the responsibility for identifying such classes into the JDK + // code itself. See tracking RFE JDK-8342481. + static AllowedSpec indy_specs[] = { + {"java/lang/constant/ConstantDescs"}, + {"java/lang/constant/DynamicConstantDesc"}, + {"java/lang/invoke/BoundMethodHandle"}, + {"java/lang/invoke/BoundMethodHandle$Specializer"}, + {"java/lang/invoke/BoundMethodHandle$Species_", IS_PREFIX}, + {"java/lang/invoke/ClassSpecializer"}, + {"java/lang/invoke/ClassSpecializer$", IS_PREFIX}, + {"java/lang/invoke/DelegatingMethodHandle"}, + {"java/lang/invoke/DelegatingMethodHandle$Holder"}, // UNSAFE.ensureClassInitialized() + {"java/lang/invoke/DirectMethodHandle"}, + {"java/lang/invoke/DirectMethodHandle$Constructor"}, + {"java/lang/invoke/DirectMethodHandle$Holder"}, // UNSAFE.ensureClassInitialized() + {"java/lang/invoke/Invokers"}, + {"java/lang/invoke/Invokers$Holder"}, // UNSAFE.ensureClassInitialized() + {"java/lang/invoke/LambdaForm"}, + {"java/lang/invoke/LambdaForm$Holder"}, // UNSAFE.ensureClassInitialized() + {"java/lang/invoke/LambdaForm$NamedFunction"}, + {"java/lang/invoke/MethodHandle"}, + {"java/lang/invoke/MethodHandles"}, + {"java/lang/invoke/SimpleMethodHandle"}, + {"java/util/Collections"}, + {"java/util/stream/Collectors"}, + {"jdk/internal/constant/ConstantUtils"}, + {"jdk/internal/constant/PrimitiveClassDescImpl"}, + {"jdk/internal/constant/ReferenceClassDescImpl"}, + + // Can't include this, as it will pull in MethodHandleStatics which has many environment + // dependencies (on system properties, etc). + // MethodHandleStatics is an example of a class that must NOT get the aot-init treatment, + // because of its strong reliance on (a) final fields which are (b) environmentally determined. + //{"java/lang/invoke/InvokerBytecodeGenerator"}, + + {nullptr} + }; + if (is_allowed(indy_specs, ik)) { + return true; + } + } + + return false; +} + +// TODO: currently we have a hard-coded list. We should turn this into +// an annotation: @jdk.internal.vm.annotation.RuntimeSetupRequired +// See JDK-8342481. +bool AOTClassInitializer::is_runtime_setup_required(InstanceKlass* ik) { + return ik == vmClasses::Class_klass() || + ik == vmClasses::internal_Unsafe_klass() || + ik == vmClasses::ConcurrentHashMap_klass(); +} + +void AOTClassInitializer::call_runtime_setup(JavaThread* current, InstanceKlass* ik) { + assert(ik->has_aot_initialized_mirror(), "sanity"); + if (ik->is_runtime_setup_required()) { + if (log_is_enabled(Info, cds, init)) { + ResourceMark rm; + log_info(cds, init)("Calling %s::runtimeSetup()", ik->external_name()); + } + JavaValue result(T_VOID); + JavaCalls::call_static(&result, ik, + vmSymbols::runtimeSetup(), + vmSymbols::void_method_signature(), current); + if (current->has_pending_exception()) { + // We cannot continue, as we might have cached instances of ik in the heap, but propagating the + // exception would cause ik to be in an error state. + AOTLinkedClassBulkLoader::exit_on_exception(current); + } + } +} + diff --git a/src/hotspot/share/cds/aotClassInitializer.hpp b/src/hotspot/share/cds/aotClassInitializer.hpp new file mode 100644 index 00000000000..c8693a0add3 --- /dev/null +++ b/src/hotspot/share/cds/aotClassInitializer.hpp @@ -0,0 +1,46 @@ +/* + * 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. + * + */ + +#ifndef SHARE_CDS_AOTCLASSINITIALIZER_HPP +#define SHARE_CDS_AOTCLASSINITIALIZER_HPP + +#include "memory/allStatic.hpp" +#include "utilities/exceptions.hpp" + +class InstanceKlass; + +class AOTClassInitializer : AllStatic { + class AllowedSpec; + static bool is_allowed(AllowedSpec* specs, InstanceKlass* ik); + +public: + // Called by heapShared.cpp to see if src_ik->java_mirror() can be archived in + // the initialized state. + static bool can_archive_initialized_mirror(InstanceKlass* src_ik); + + static bool is_runtime_setup_required(InstanceKlass* ik); + static void call_runtime_setup(JavaThread* current, InstanceKlass* ik); +}; + +#endif // SHARE_CDS_AOTCLASSINITIALIZER_HPP diff --git a/src/hotspot/share/cds/aotClassLinker.cpp b/src/hotspot/share/cds/aotClassLinker.cpp new file mode 100644 index 00000000000..8525ce928a8 --- /dev/null +++ b/src/hotspot/share/cds/aotClassLinker.cpp @@ -0,0 +1,319 @@ +/* + * 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. + * + */ + +#include "precompiled.hpp" +#include "cds/aotClassLinker.hpp" +#include "cds/aotConstantPoolResolver.hpp" +#include "cds/aotLinkedClassTable.hpp" +#include "cds/archiveBuilder.hpp" +#include "cds/archiveUtils.inline.hpp" +#include "cds/cdsConfig.hpp" +#include "cds/heapShared.hpp" +#include "cds/lambdaFormInvokers.inline.hpp" +#include "classfile/classLoader.hpp" +#include "classfile/dictionary.hpp" +#include "classfile/systemDictionary.hpp" +#include "classfile/systemDictionaryShared.hpp" +#include "classfile/vmClasses.hpp" +#include "memory/resourceArea.hpp" +#include "oops/constantPool.inline.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/klass.inline.hpp" +#include "runtime/handles.inline.hpp" + +AOTClassLinker::ClassesTable* AOTClassLinker::_vm_classes = nullptr; +AOTClassLinker::ClassesTable* AOTClassLinker::_candidates = nullptr; +GrowableArrayCHeap* AOTClassLinker::_sorted_candidates = nullptr; + +#ifdef ASSERT +bool AOTClassLinker::is_initialized() { + assert(CDSConfig::is_dumping_archive(), "AOTClassLinker is for CDS dumping only"); + return _vm_classes != nullptr; +} +#endif + +void AOTClassLinker::initialize() { + assert(!is_initialized(), "sanity"); + + _vm_classes = new (mtClass)ClassesTable(); + _candidates = new (mtClass)ClassesTable(); + _sorted_candidates = new GrowableArrayCHeap(1000); + + for (auto id : EnumRange{}) { + add_vm_class(vmClasses::klass_at(id)); + } + + assert(is_initialized(), "sanity"); + + AOTConstantPoolResolver::initialize(); +} + +void AOTClassLinker::dispose() { + assert(is_initialized(), "sanity"); + + delete _vm_classes; + delete _candidates; + delete _sorted_candidates; + _vm_classes = nullptr; + _candidates = nullptr; + _sorted_candidates = nullptr; + + assert(!is_initialized(), "sanity"); + + AOTConstantPoolResolver::dispose(); +} + +bool AOTClassLinker::is_vm_class(InstanceKlass* ik) { + assert(is_initialized(), "sanity"); + return (_vm_classes->get(ik) != nullptr); +} + +void AOTClassLinker::add_vm_class(InstanceKlass* ik) { + assert(is_initialized(), "sanity"); + bool created; + _vm_classes->put_if_absent(ik, &created); + if (created) { + if (CDSConfig::is_dumping_aot_linked_classes()) { + bool v = try_add_candidate(ik); + assert(v, "must succeed for VM class"); + } + InstanceKlass* super = ik->java_super(); + if (super != nullptr) { + add_vm_class(super); + } + Array* ifs = ik->local_interfaces(); + for (int i = 0; i < ifs->length(); i++) { + add_vm_class(ifs->at(i)); + } + } +} + +bool AOTClassLinker::is_candidate(InstanceKlass* ik) { + return (_candidates->get(ik) != nullptr); +} + +void AOTClassLinker::add_new_candidate(InstanceKlass* ik) { + assert(!is_candidate(ik), "caller need to check"); + _candidates->put_when_absent(ik, true); + _sorted_candidates->append(ik); + + if (log_is_enabled(Info, cds, aot, link)) { + ResourceMark rm; + log_info(cds, aot, link)("%s %s %p", class_category_name(ik), ik->external_name(), ik); + } +} + +// ik is a candidate for aot-linking; see if it can really work +// that way, and return success or failure. Not only must ik itself +// look like a class that can be aot-linked but its supers must also be +// aot-linkable. +bool AOTClassLinker::try_add_candidate(InstanceKlass* ik) { + assert(is_initialized(), "sanity"); + assert(CDSConfig::is_dumping_aot_linked_classes(), "sanity"); + + if (!SystemDictionaryShared::is_builtin(ik)) { + // not loaded by a class loader which we know about + return false; + } + + if (is_candidate(ik)) { // already checked. + return true; + } + + if (ik->is_hidden()) { + assert(ik->shared_class_loader_type() != ClassLoader::OTHER, "must have been set"); + if (!CDSConfig::is_dumping_invokedynamic()) { + return false; + } + if (!SystemDictionaryShared::should_hidden_class_be_archived(ik)) { + return false; + } + if (HeapShared::is_lambda_proxy_klass(ik)) { + InstanceKlass* nest_host = ik->nest_host_not_null(); + if (!try_add_candidate(nest_host)) { + ResourceMark rm; + log_warning(cds, aot, link)("%s cannot be aot-linked because it nest host is not aot-linked", ik->external_name()); + return false; + } + } + } + + InstanceKlass* s = ik->java_super(); + if (s != nullptr && !try_add_candidate(s)) { + return false; + } + + Array* interfaces = ik->local_interfaces(); + int num_interfaces = interfaces->length(); + for (int index = 0; index < num_interfaces; index++) { + InstanceKlass* intf = interfaces->at(index); + if (!try_add_candidate(intf)) { + return false; + } + } + + // There are no loops in the class hierarchy, and this function is always called single-threaded, so + // we know ik has not been added yet. + assert(CDSConfig::current_thread_is_vm_or_dumper(), "that's why we don't need locks"); + add_new_candidate(ik); + + return true; +} + +void AOTClassLinker::add_candidates() { + assert_at_safepoint(); + if (CDSConfig::is_dumping_aot_linked_classes()) { + GrowableArray* klasses = ArchiveBuilder::current()->klasses(); + for (GrowableArrayIterator it = klasses->begin(); it != klasses->end(); ++it) { + Klass* k = *it; + if (k->is_instance_klass()) { + try_add_candidate(InstanceKlass::cast(k)); + } + } + } +} + +void AOTClassLinker::write_to_archive() { + assert(is_initialized(), "sanity"); + assert_at_safepoint(); + + if (CDSConfig::is_dumping_aot_linked_classes()) { + AOTLinkedClassTable* table = AOTLinkedClassTable::get(CDSConfig::is_dumping_static_archive()); + table->set_boot(write_classes(nullptr, true)); + table->set_boot2(write_classes(nullptr, false)); + table->set_platform(write_classes(SystemDictionary::java_platform_loader(), false)); + table->set_app(write_classes(SystemDictionary::java_system_loader(), false)); + } +} + +Array* AOTClassLinker::write_classes(oop class_loader, bool is_javabase) { + ResourceMark rm; + GrowableArray list; + + for (int i = 0; i < _sorted_candidates->length(); i++) { + InstanceKlass* ik = _sorted_candidates->at(i); + if (ik->class_loader() != class_loader) { + continue; + } + if ((ik->module() == ModuleEntryTable::javabase_moduleEntry()) != is_javabase) { + continue; + } + + if (ik->is_shared() && CDSConfig::is_dumping_dynamic_archive()) { + if (CDSConfig::is_using_aot_linked_classes()) { + // This class was recorded as AOT-linked for the base archive, + // so there's no need to do so again for the dynamic archive. + } else { + list.append(ik); + } + } else { + list.append(ArchiveBuilder::current()->get_buffered_addr(ik)); + } + } + + if (list.length() == 0) { + return nullptr; + } else { + const char* category = class_category_name(list.at(0)); + log_info(cds, aot, link)("wrote %d class(es) for category %s", list.length(), category); + return ArchiveUtils::archive_array(&list); + } +} + +int AOTClassLinker::num_platform_initiated_classes() { + if (CDSConfig::is_dumping_aot_linked_classes()) { + // AOTLinkedClassBulkLoader will initiate loading of all public boot classes in the platform loader. + return count_public_classes(nullptr); + } else { + return 0; + } +} + +int AOTClassLinker::num_app_initiated_classes() { + if (CDSConfig::is_dumping_aot_linked_classes()) { + // AOTLinkedClassBulkLoader will initiate loading of all public boot/platform classes in the app loader. + return count_public_classes(nullptr) + count_public_classes(SystemDictionary::java_platform_loader()); + } else { + return 0; + } +} + +int AOTClassLinker::count_public_classes(oop loader) { + int n = 0; + for (int i = 0; i < _sorted_candidates->length(); i++) { + InstanceKlass* ik = _sorted_candidates->at(i); + if (ik->is_public() && !ik->is_hidden() && ik->class_loader() == loader) { + n++; + } + } + + return n; +} + +// Used in logging: "boot1", "boot2", "plat", "app" and "unreg", or "array" +const char* AOTClassLinker::class_category_name(Klass* k) { + if (ArchiveBuilder::is_active() && ArchiveBuilder::current()->is_in_buffer_space(k)) { + k = ArchiveBuilder::current()->get_source_addr(k); + } + + if (k->is_array_klass()) { + return "array"; + } else { + oop loader = k->class_loader(); + if (loader == nullptr) { + if (k->module() != nullptr && + k->module()->name() != nullptr && + k->module()->name()->equals("java.base")) { + return "boot1"; // boot classes in java.base are loaded in the 1st phase + } else { + return "boot2"; // boot classes outside of java.base are loaded in the 2nd phase phase + } + } else { + if (loader == SystemDictionary::java_platform_loader()) { + return "plat"; + } else if (loader == SystemDictionary::java_system_loader()) { + return "app"; + } else { + return "unreg"; + } + } + } +} + +const char* AOTClassLinker::class_category_name(AOTLinkedClassCategory category) { + switch (category) { + case AOTLinkedClassCategory::BOOT1: + return "boot1"; + case AOTLinkedClassCategory::BOOT2: + return "boot2"; + case AOTLinkedClassCategory::PLATFORM: + return "plat"; + case AOTLinkedClassCategory::APP: + return "app"; + case AOTLinkedClassCategory::UNREGISTERED: + default: + return "unreg"; + } +} + diff --git a/src/hotspot/share/cds/aotClassLinker.hpp b/src/hotspot/share/cds/aotClassLinker.hpp new file mode 100644 index 00000000000..f15684a1ad2 --- /dev/null +++ b/src/hotspot/share/cds/aotClassLinker.hpp @@ -0,0 +1,129 @@ +/* + * 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. + * + */ + +#ifndef SHARE_CDS_AOTCLASSLINKER_HPP +#define SHARE_CDS_AOTCLASSLINKER_HPP + +#include "interpreter/bytecodes.hpp" +#include "memory/allocation.hpp" +#include "memory/allStatic.hpp" +#include "oops/oopsHierarchy.hpp" +#include "utilities/exceptions.hpp" +#include "utilities/growableArray.hpp" +#include "utilities/macros.hpp" +#include "utilities/resourceHash.hpp" + +class AOTLinkedClassTable; +class InstanceKlass; +class SerializeClosure; +template class Array; +enum class AOTLinkedClassCategory : int; + +// AOTClassLinker is used during the AOTCache Assembly Phase. +// It links eligible classes before they are written into the AOTCache +// +// The classes linked by AOTClassLinker are recorded in an AOTLinkedClassTable, +// which is also written into the AOTCache. +// +// AOTClassLinker is enabled by the -XX:+AOTClassLinking option. If this option +// is disabled, an empty AOTLinkedClassTable will be included in the AOTCache. +// +// For each class C in the AOTLinkedClassTable, the following properties for C +// are assigned by AOTClassLinker and cannot be changed thereafter. +// - The CodeSource for C +// - The bytecodes in C +// - The supertypes of C +// - The ClassLoader, Package and Module of C +// - The visibility of C +// +// During a production run, the JVM can use an AOTCache with an AOTLinkedClassTable +// only if it's guaranteed to produce the same results for the above set of properties +// for each class C in the AOTLinkedClassTable. +// +// For example, +// - C may be loaded from a different CodeSource when the CLASSPATH is changed. +// - Some JVMTI agent may allow the bytecodes of C to be modified. +// - C may be made invisible by module options such as --add-modules +// In such situations, the JVM will refuse to load the AOTCache. +// +class AOTClassLinker : AllStatic { + static const int TABLE_SIZE = 15889; // prime number + using ClassesTable = ResourceHashtable; + + // Classes loaded inside vmClasses::resolve_all() + static ClassesTable* _vm_classes; + + // Classes that should be automatically loaded into system dictionary at VM start-up + static ClassesTable* _candidates; + + // Sorted list such that super types come first. + static GrowableArrayCHeap* _sorted_candidates; + + DEBUG_ONLY(static bool is_initialized()); + + static void add_vm_class(InstanceKlass* ik); + static void add_new_candidate(InstanceKlass* ik); + + static Array* write_classes(oop class_loader, bool is_javabase); + static int count_public_classes(oop loader); + +public: + static void initialize(); + static void add_candidates(); + static void write_to_archive(); + static void dispose(); + + // Is this class resolved as part of vmClasses::resolve_all()? + static bool is_vm_class(InstanceKlass* ik); + + // When CDS is enabled, is ik guaranteed to be linked at deployment time (and + // cannot be replaced by JVMTI, etc)? + // This is a necessary (but not sufficient) condition for keeping a direct pointer + // to ik in AOT-computed data (such as ConstantPool entries in archived classes, + // or in AOT-compiled code). + static bool is_candidate(InstanceKlass* ik); + + // Request that ik be added to the candidates table. This will return true only if + // ik is allowed to be aot-linked. + static bool try_add_candidate(InstanceKlass* ik); + + static int num_app_initiated_classes(); + static int num_platform_initiated_classes(); + + // Used in logging: "boot1", "boot2", "plat", "app" and "unreg"; + static const char* class_category_name(AOTLinkedClassCategory category); + static const char* class_category_name(Klass* k); +}; + +// AOT-linked classes are divided into different categories and are loaded +// in two phases during the production run. +enum class AOTLinkedClassCategory : int { + BOOT1, // Only java.base classes are loaded in the 1st phase + BOOT2, // All boot classes that not in java.base are loaded in the 2nd phase + PLATFORM, // Classes for platform loader, loaded in the 2nd phase + APP, // Classes for the app loader, loaded in the 2nd phase + UNREGISTERED // classes loaded outside of the boot/platform/app loaders; currently not supported by AOTClassLinker +}; + +#endif // SHARE_CDS_AOTCLASSLINKER_HPP diff --git a/src/hotspot/share/cds/aotConstantPoolResolver.cpp b/src/hotspot/share/cds/aotConstantPoolResolver.cpp new file mode 100644 index 00000000000..cc500557c8d --- /dev/null +++ b/src/hotspot/share/cds/aotConstantPoolResolver.cpp @@ -0,0 +1,573 @@ +/* + * 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 + * 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. + * + */ + +#include "precompiled.hpp" +#include "cds/aotClassLinker.hpp" +#include "cds/aotConstantPoolResolver.hpp" +#include "cds/archiveBuilder.hpp" +#include "cds/cdsConfig.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" +#include "oops/klass.inline.hpp" +#include "runtime/handles.inline.hpp" + +AOTConstantPoolResolver::ClassesTable* AOTConstantPoolResolver::_processed_classes = nullptr; + +void AOTConstantPoolResolver::initialize() { + assert(_processed_classes == nullptr, "must be"); + _processed_classes = new (mtClass)ClassesTable(); +} + +void AOTConstantPoolResolver::dispose() { + assert(_processed_classes != nullptr, "must be"); + delete _processed_classes; + _processed_classes = nullptr; +} + +// 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 AOTConstantPoolResolver::is_resolution_deterministic(ConstantPool* cp, int cp_index) { + assert(!is_in_archivebuilder_buffer(cp), "sanity"); + + 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_invoke_dynamic()) { + return is_indy_resolution_deterministic(cp, cp_index); + } else if (cp->tag_at(cp_index).is_field() || + cp->tag_at(cp_index).is_method() || + cp->tag_at(cp_index).is_interface_method()) { + 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; + } + + 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 AOTConstantPoolResolver::is_class_resolution_deterministic(InstanceKlass* cp_holder, Klass* resolved_class) { + assert(!is_in_archivebuilder_buffer(cp_holder), "sanity"); + assert(!is_in_archivebuilder_buffer(resolved_class), "sanity"); + + if (resolved_class->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(resolved_class); + + if (!ik->is_shared() && SystemDictionaryShared::is_excluded_class(ik)) { + return false; + } + + if (cp_holder->is_subtype_of(ik)) { + // All super types of ik will be resolved in ik->class_loader() before + // ik is defined in this loader, so it's safe to archive the resolved klass reference. + return true; + } + + if (CDSConfig::is_dumping_aot_linked_classes()) { + // Need to call try_add_candidate instead of is_candidate, as this may be called + // before AOTClassLinker::add_candidates(). + if (AOTClassLinker::try_add_candidate(ik)) { + return true; + } else { + return false; + } + } else if (AOTClassLinker::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 { + return false; + } + } 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 { + return false; + } + } else if (resolved_class->is_typeArray_klass()) { + return true; + } else { + return false; + } +} + +void AOTConstantPoolResolver::dumptime_resolve_constants(InstanceKlass* ik, TRAPS) { + if (!ik->is_linked()) { + return; + } + bool first_time; + _processed_classes->put_if_absent(ik, &first_time); + if (!first_time) { + // We have already resolved the constants in class, so no need to do it again. + 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_String: + resolve_string(cp, cp_index, CHECK); // may throw OOM when interning strings. + break; + } + } +} + +// This works only for the boot/platform/app loaders +Klass* AOTConstantPoolResolver::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 (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
(h_loader()), + cast_from_oop
(SystemDictionary::java_system_loader()), + cast_from_oop
(SystemDictionary::java_platform_loader())); + } + + return nullptr; +} + +Klass* AOTConstantPoolResolver::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 +void AOTConstantPoolResolver::resolve_string(constantPoolHandle cp, int cp_index, TRAPS) { + if (CDSConfig::is_dumping_heap()) { + int cache_index = cp->cp_to_object_index(cp_index); + ConstantPool::string_at_impl(cp, cp_index, cache_index, CHECK); + } +} +#endif + +void AOTConstantPoolResolver::preresolve_class_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* 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 AOTConstantPoolResolver::preresolve_field_and_method_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* 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; + case Bytecodes::_invokehandle: + case Bytecodes::_invokespecial: + case Bytecodes::_invokevirtual: + case Bytecodes::_invokeinterface: + 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 AOTConstantPoolResolver::maybe_resolve_fmi_ref(InstanceKlass* ik, Method* m, Bytecodes::Code bc, int raw_index, + GrowableArray* 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; + + case Bytecodes::_invokevirtual: + case Bytecodes::_invokespecial: + case Bytecodes::_invokeinterface: + InterpreterRuntime::cds_resolve_invoke(bc, raw_index, cp, CHECK); + break; + + case Bytecodes::_invokehandle: + InterpreterRuntime::cds_resolve_invokehandle(raw_index, cp, 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()); + } +} + +void AOTConstantPoolResolver::preresolve_indy_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* preresolve_list) { + JavaThread* THREAD = current; + constantPoolHandle cp(THREAD, ik->constants()); + if (!CDSConfig::is_dumping_invokedynamic() || cp->cache() == nullptr) { + return; + } + + assert(preresolve_list != nullptr, "preresolve_indy_cp_entries() should not be called for " + "regenerated LambdaForm Invoker classes, which should not have indys anyway."); + + Array* indy_entries = cp->cache()->resolved_indy_entries(); + for (int i = 0; i < indy_entries->length(); i++) { + ResolvedIndyEntry* rie = indy_entries->adr_at(i); + int cp_index = rie->constant_pool_index(); + if (preresolve_list->at(cp_index) == true) { + if (!rie->is_resolved() && is_indy_resolution_deterministic(cp(), cp_index)) { + InterpreterRuntime::cds_resolve_invokedynamic(i, cp, THREAD); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; // just ignore + } + } + if (log_is_enabled(Trace, cds, resolve)) { + ResourceMark rm(THREAD); + log_trace(cds, resolve)("%s indy [%3d] %s", + rie->is_resolved() ? "Resolved" : "Failed to resolve", + cp_index, ik->external_name()); + } + } + } +} + +// Check the MethodType signatures used by parameters to the indy BSMs. Make sure we don't +// use types that have been excluded, or else we might end up creating MethodTypes that cannot be stored +// in the AOT cache. +bool AOTConstantPoolResolver::check_methodtype_signature(ConstantPool* cp, Symbol* sig, Klass** return_type_ret) { + ResourceMark rm; + for (SignatureStream ss(sig); !ss.is_done(); ss.next()) { + if (ss.is_reference()) { + Symbol* type = ss.as_symbol(); + Klass* k = find_loaded_class(Thread::current(), cp->pool_holder()->class_loader(), type); + if (k == nullptr) { + return false; + } + + if (SystemDictionaryShared::should_be_excluded(k)) { + if (log_is_enabled(Warning, cds, resolve)) { + ResourceMark rm; + log_warning(cds, resolve)("Cannot aot-resolve Lambda proxy because %s is excluded", k->external_name()); + } + return false; + } + + if (ss.at_return_type() && return_type_ret != nullptr) { + *return_type_ret = k; + } + } + } + return true; +} + +bool AOTConstantPoolResolver::check_lambda_metafactory_signature(ConstantPool* cp, Symbol* sig) { + Klass* k; + if (!check_methodtype_signature(cp, sig, &k)) { + return false; + } + + // is the interface type implemented by the lambda proxy + if (!k->is_interface()) { + // cp->pool_holder() doesn't look like a valid class generated by javac + return false; + } + + + // The linked lambda callsite has an instance of the interface implemented by this lambda. If this + // interface requires its to be executed, then we must delay the execution to the production run + // as can have side effects ==> exclude such cases. + InstanceKlass* intf = InstanceKlass::cast(k); + bool exclude = intf->interface_needs_clinit_execution_as_super(); + if (log_is_enabled(Debug, cds, resolve)) { + ResourceMark rm; + log_debug(cds, resolve)("%s aot-resolve Lambda proxy of interface type %s", + exclude ? "Cannot" : "Can", k->external_name()); + } + return !exclude; +} + +bool AOTConstantPoolResolver::check_lambda_metafactory_methodtype_arg(ConstantPool* cp, int bsms_attribute_index, int arg_i) { + int mt_index = cp->operand_argument_index_at(bsms_attribute_index, arg_i); + if (!cp->tag_at(mt_index).is_method_type()) { + // malformed class? + return false; + } + + Symbol* sig = cp->method_type_signature_at(mt_index); + if (log_is_enabled(Debug, cds, resolve)) { + ResourceMark rm; + log_debug(cds, resolve)("Checking MethodType for LambdaMetafactory BSM arg %d: %s", arg_i, sig->as_C_string()); + } + + return check_methodtype_signature(cp, sig); +} + +bool AOTConstantPoolResolver::check_lambda_metafactory_methodhandle_arg(ConstantPool* cp, int bsms_attribute_index, int arg_i) { + int mh_index = cp->operand_argument_index_at(bsms_attribute_index, arg_i); + if (!cp->tag_at(mh_index).is_method_handle()) { + // malformed class? + return false; + } + + Symbol* sig = cp->method_handle_signature_ref_at(mh_index); + if (log_is_enabled(Debug, cds, resolve)) { + ResourceMark rm; + log_debug(cds, resolve)("Checking MethodType of MethodHandle for LambdaMetafactory BSM arg %d: %s", arg_i, sig->as_C_string()); + } + return check_methodtype_signature(cp, sig); +} + +bool AOTConstantPoolResolver::is_indy_resolution_deterministic(ConstantPool* cp, int cp_index) { + assert(cp->tag_at(cp_index).is_invoke_dynamic(), "sanity"); + if (!CDSConfig::is_dumping_invokedynamic()) { + return false; + } + + InstanceKlass* pool_holder = cp->pool_holder(); + if (!SystemDictionaryShared::is_builtin(pool_holder)) { + return false; + } + + int bsm = cp->bootstrap_method_ref_index_at(cp_index); + int bsm_ref = cp->method_handle_index_at(bsm); + Symbol* bsm_name = cp->uncached_name_ref_at(bsm_ref); + Symbol* bsm_signature = cp->uncached_signature_ref_at(bsm_ref); + Symbol* bsm_klass = cp->klass_name_at(cp->uncached_klass_ref_index_at(bsm_ref)); + + // We currently support only StringConcatFactory::makeConcatWithConstants() and LambdaMetafactory::metafactory() + // We should mark the allowed BSMs in the JDK code using a private annotation. + // See notes on RFE JDK-8342481. + + if (bsm_klass->equals("java/lang/invoke/StringConcatFactory") && + bsm_name->equals("makeConcatWithConstants") && + bsm_signature->equals("(Ljava/lang/invoke/MethodHandles$Lookup;" + "Ljava/lang/String;" + "Ljava/lang/invoke/MethodType;" + "Ljava/lang/String;" + "[Ljava/lang/Object;" + ")Ljava/lang/invoke/CallSite;")) { + Symbol* factory_type_sig = cp->uncached_signature_ref_at(cp_index); + if (log_is_enabled(Debug, cds, resolve)) { + ResourceMark rm; + log_debug(cds, resolve)("Checking StringConcatFactory callsite signature [%d]: %s", cp_index, factory_type_sig->as_C_string()); + } + + Klass* k; + if (!check_methodtype_signature(cp, factory_type_sig, &k)) { + return false; + } + if (k != vmClasses::String_klass()) { + // bad class file? + return false; + } + + return true; + } + + if (bsm_klass->equals("java/lang/invoke/LambdaMetafactory") && + bsm_name->equals("metafactory") && + bsm_signature->equals("(Ljava/lang/invoke/MethodHandles$Lookup;" + "Ljava/lang/String;" + "Ljava/lang/invoke/MethodType;" + "Ljava/lang/invoke/MethodType;" + "Ljava/lang/invoke/MethodHandle;" + "Ljava/lang/invoke/MethodType;" + ")Ljava/lang/invoke/CallSite;")) { + /* + * An indy callsite is associated with the following MethodType and MethodHandles: + * + * https://github.com/openjdk/jdk/blob/580eb62dc097efeb51c76b095c1404106859b673/src/java.base/share/classes/java/lang/invoke/LambdaMetafactory.java#L293-L309 + * + * MethodType factoryType The expected signature of the {@code CallSite}. The + * parameter types represent the types of capture variables; + * the return type is the interface to implement. When + * used with {@code invokedynamic}, this is provided by + * the {@code NameAndType} of the {@code InvokeDynamic} + * + * MethodType interfaceMethodType Signature and return type of method to be + * implemented by the function object. + * + * MethodHandle implementation A direct method handle describing the implementation + * method which should be called (with suitable adaptation + * of argument types and return types, and with captured + * arguments prepended to the invocation arguments) at + * invocation time. + * + * MethodType dynamicMethodType The signature and return type that should + * be enforced dynamically at invocation time. + * In simple use cases this is the same as + * {@code interfaceMethodType}. + */ + Symbol* factory_type_sig = cp->uncached_signature_ref_at(cp_index); + if (log_is_enabled(Debug, cds, resolve)) { + ResourceMark rm; + log_debug(cds, resolve)("Checking indy callsite signature [%d]: %s", cp_index, factory_type_sig->as_C_string()); + } + + if (!check_lambda_metafactory_signature(cp, factory_type_sig)) { + return false; + } + + int bsms_attribute_index = cp->bootstrap_methods_attribute_index(cp_index); + int arg_count = cp->operand_argument_count_at(bsms_attribute_index); + if (arg_count != 3) { + // Malformed class? + return false; + } + + // interfaceMethodType + if (!check_lambda_metafactory_methodtype_arg(cp, bsms_attribute_index, 0)) { + return false; + } + + // implementation + if (!check_lambda_metafactory_methodhandle_arg(cp, bsms_attribute_index, 1)) { + return false; + } + + // dynamicMethodType + if (!check_lambda_metafactory_methodtype_arg(cp, bsms_attribute_index, 2)) { + return false; + } + + return true; + } + + return false; +} +#ifdef ASSERT +bool AOTConstantPoolResolver::is_in_archivebuilder_buffer(address p) { + if (!Thread::current()->is_VM_thread() || ArchiveBuilder::current() == nullptr) { + return false; + } else { + return ArchiveBuilder::current()->is_in_buffer_space(p); + } +} +#endif diff --git a/src/hotspot/share/cds/classPrelinker.hpp b/src/hotspot/share/cds/aotConstantPoolResolver.hpp similarity index 73% rename from src/hotspot/share/cds/classPrelinker.hpp rename to src/hotspot/share/cds/aotConstantPoolResolver.hpp index 41588961d8b..2f65849fbb4 100644 --- a/src/hotspot/share/cds/classPrelinker.hpp +++ b/src/hotspot/share/cds/aotConstantPoolResolver.hpp @@ -22,13 +22,13 @@ * */ -#ifndef SHARE_CDS_CLASSPRELINKER_HPP -#define SHARE_CDS_CLASSPRELINKER_HPP +#ifndef SHARE_CDS_AOTCONSTANTPOOLRESOLVER_HPP +#define SHARE_CDS_AOTCONSTANTPOOLRESOLVER_HPP #include "interpreter/bytecodes.hpp" -#include "oops/oopsHierarchy.hpp" #include "memory/allStatic.hpp" #include "memory/allocation.hpp" +#include "oops/oopsHierarchy.hpp" #include "runtime/handles.hpp" #include "utilities/exceptions.hpp" #include "utilities/macros.hpp" @@ -39,7 +39,9 @@ class constantPoolHandle; class InstanceKlass; class Klass; -// ClassPrelinker is used to perform ahead-of-time linking of ConstantPool entries +template class GrowableArray; + +// AOTConstantPoolResolver is used to perform ahead-of-time linking of ConstantPool entries // for archived InstanceKlasses. // // At run time, Java classes are loaded dynamically and may be replaced with JVMTI. @@ -49,23 +51,21 @@ class Klass; // For example, a JVM_CONSTANT_Class reference to a supertype can be safely resolved // at dump time, because at run time we will load a class from the CDS archive only // if all of its supertypes are loaded from the CDS archive. -class ClassPrelinker : AllStatic { - using ClassesTable = ResourceHashtable ; +class AOTConstantPoolResolver : AllStatic { + static const int TABLE_SIZE = 15889; // prime number + using ClassesTable = ResourceHashtable ; static ClassesTable* _processed_classes; - static ClassesTable* _vm_classes; - - static void add_one_vm_class(InstanceKlass* ik); #ifdef ASSERT + template static bool is_in_archivebuilder_buffer(T p) { + return is_in_archivebuilder_buffer((address)(p)); + } static bool is_in_archivebuilder_buffer(address p); #endif - template - static bool is_in_archivebuilder_buffer(T p) { - return is_in_archivebuilder_buffer((address)(p)); - } static void resolve_string(constantPoolHandle cp, int cp_index, TRAPS) NOT_CDS_JAVA_HEAP_RETURN; static bool is_class_resolution_deterministic(InstanceKlass* cp_holder, Klass* resolved_class); + static bool is_indy_resolution_deterministic(ConstantPool* cp, int cp_index); 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); @@ -73,18 +73,20 @@ class ClassPrelinker : AllStatic { // fmi = FieldRef/MethodRef/InterfaceMethodRef static void maybe_resolve_fmi_ref(InstanceKlass* ik, Method* m, Bytecodes::Code bc, int raw_index, GrowableArray* resolve_fmi_list, TRAPS); + + static bool check_methodtype_signature(ConstantPool* cp, Symbol* sig, Klass** return_type_ret = nullptr); + static bool check_lambda_metafactory_signature(ConstantPool* cp, Symbol* sig); + static bool check_lambda_metafactory_methodtype_arg(ConstantPool* cp, int bsms_attribute_index, int arg_i); + static bool check_lambda_metafactory_methodhandle_arg(ConstantPool* cp, int bsms_attribute_index, int arg_i); + public: static void initialize(); static void dispose(); static void preresolve_class_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* preresolve_list); static void preresolve_field_and_method_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* preresolve_list); + static void preresolve_indy_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* 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 - // classes. - static bool is_vm_class(InstanceKlass* ik); // Resolve all constant pool entries that are safe to be stored in the // CDS archive. @@ -93,4 +95,4 @@ public: static bool is_resolution_deterministic(ConstantPool* cp, int cp_index); }; -#endif // SHARE_CDS_CLASSPRELINKER_HPP +#endif // SHARE_CDS_AOTCONSTANTPOOLRESOLVER_HPP diff --git a/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp b/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp new file mode 100644 index 00000000000..d823db9e8d7 --- /dev/null +++ b/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp @@ -0,0 +1,397 @@ +/* + * 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. + * + */ + +#include "precompiled.hpp" +#include "cds/aotClassInitializer.hpp" +#include "cds/aotClassLinker.hpp" +#include "cds/aotLinkedClassBulkLoader.hpp" +#include "cds/aotLinkedClassTable.hpp" +#include "cds/cdsConfig.hpp" +#include "cds/heapShared.hpp" +#include "classfile/classLoaderData.hpp" +#include "classfile/systemDictionary.hpp" +#include "classfile/systemDictionaryShared.hpp" +#include "classfile/vmClasses.hpp" +#include "gc/shared/gcVMOperations.hpp" +#include "memory/resourceArea.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/klass.inline.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/java.hpp" + +bool AOTLinkedClassBulkLoader::_boot2_completed = false; +bool AOTLinkedClassBulkLoader::_platform_completed = false; +bool AOTLinkedClassBulkLoader::_app_completed = false; +bool AOTLinkedClassBulkLoader::_all_completed = false; + +void AOTLinkedClassBulkLoader::serialize(SerializeClosure* soc, bool is_static_archive) { + AOTLinkedClassTable::get(is_static_archive)->serialize(soc); +} + +void AOTLinkedClassBulkLoader::load_javabase_classes(JavaThread* current) { + assert(CDSConfig::is_using_aot_linked_classes(), "sanity"); + load_classes_in_loader(current, AOTLinkedClassCategory::BOOT1, nullptr); // only java.base classes +} + +void AOTLinkedClassBulkLoader::load_non_javabase_classes(JavaThread* current) { + assert(CDSConfig::is_using_aot_linked_classes(), "sanity"); + + // is_using_aot_linked_classes() requires is_using_full_module_graph(). As a result, + // the platform/system class loader should already have been initialized as part + // of the FMG support. + assert(CDSConfig::is_using_full_module_graph(), "must be"); + assert(SystemDictionary::java_platform_loader() != nullptr, "must be"); + assert(SystemDictionary::java_system_loader() != nullptr, "must be"); + + load_classes_in_loader(current, AOTLinkedClassCategory::BOOT2, nullptr); // all boot classes outside of java.base + _boot2_completed = true; + + load_classes_in_loader(current, AOTLinkedClassCategory::PLATFORM, SystemDictionary::java_platform_loader()); + _platform_completed = true; + + load_classes_in_loader(current, AOTLinkedClassCategory::APP, SystemDictionary::java_system_loader()); + _app_completed = true; + _all_completed = true; +} + +void AOTLinkedClassBulkLoader::load_classes_in_loader(JavaThread* current, AOTLinkedClassCategory class_category, oop class_loader_oop) { + load_classes_in_loader_impl(class_category, class_loader_oop, current); + if (current->has_pending_exception()) { + // We cannot continue, as we might have loaded some of the aot-linked classes, which + // may have dangling C++ pointers to other aot-linked classes that we have failed to load. + exit_on_exception(current); + } +} + +void AOTLinkedClassBulkLoader::exit_on_exception(JavaThread* current) { + assert(current->has_pending_exception(), "precondition"); + ResourceMark rm(current); + if (current->pending_exception()->is_a(vmClasses::OutOfMemoryError_klass())) { + log_error(cds)("Out of memory. Please run with a larger Java heap, current MaxHeapSize = " + SIZE_FORMAT "M", MaxHeapSize/M); + } else { + log_error(cds)("%s: %s", current->pending_exception()->klass()->external_name(), + java_lang_String::as_utf8_string(java_lang_Throwable::message(current->pending_exception()))); + } + vm_exit_during_initialization("Unexpected exception when loading aot-linked classes."); +} + +void AOTLinkedClassBulkLoader::load_classes_in_loader_impl(AOTLinkedClassCategory class_category, oop class_loader_oop, TRAPS) { + Handle h_loader(THREAD, class_loader_oop); + load_table(AOTLinkedClassTable::for_static_archive(), class_category, h_loader, CHECK); + load_table(AOTLinkedClassTable::for_dynamic_archive(), class_category, h_loader, CHECK); + + // Initialize the InstanceKlasses of all archived heap objects that are reachable from the + // archived java class mirrors. + // + // Only the classes in the static archive can have archived mirrors. + AOTLinkedClassTable* static_table = AOTLinkedClassTable::for_static_archive(); + switch (class_category) { + case AOTLinkedClassCategory::BOOT1: + // Delayed until finish_loading_javabase_classes(), as the VM is not ready to + // execute some of the methods. + break; + case AOTLinkedClassCategory::BOOT2: + init_required_classes_for_loader(h_loader, static_table->boot2(), CHECK); + break; + case AOTLinkedClassCategory::PLATFORM: + init_required_classes_for_loader(h_loader, static_table->platform(), CHECK); + break; + case AOTLinkedClassCategory::APP: + init_required_classes_for_loader(h_loader, static_table->app(), CHECK); + break; + case AOTLinkedClassCategory::UNREGISTERED: + ShouldNotReachHere(); + break; + } + + if (Universe::is_fully_initialized() && VerifyDuringStartup) { + // Make sure we're still in a clean state. + VM_Verify verify_op; + VMThread::execute(&verify_op); + } +} + +void AOTLinkedClassBulkLoader::load_table(AOTLinkedClassTable* table, AOTLinkedClassCategory class_category, Handle loader, TRAPS) { + if (class_category != AOTLinkedClassCategory::BOOT1) { + assert(Universe::is_module_initialized(), "sanity"); + } + + const char* category_name = AOTClassLinker::class_category_name(class_category); + switch (class_category) { + case AOTLinkedClassCategory::BOOT1: + load_classes_impl(class_category, table->boot(), category_name, loader, CHECK); + break; + + case AOTLinkedClassCategory::BOOT2: + load_classes_impl(class_category, table->boot2(), category_name, loader, CHECK); + break; + + case AOTLinkedClassCategory::PLATFORM: + { + initiate_loading(THREAD, category_name, loader, table->boot()); + initiate_loading(THREAD, category_name, loader, table->boot2()); + load_classes_impl(class_category, table->platform(), category_name, loader, CHECK); + } + break; + case AOTLinkedClassCategory::APP: + { + initiate_loading(THREAD, category_name, loader, table->boot()); + initiate_loading(THREAD, category_name, loader, table->boot2()); + initiate_loading(THREAD, category_name, loader, table->platform()); + load_classes_impl(class_category, table->app(), category_name, loader, CHECK); + } + break; + case AOTLinkedClassCategory::UNREGISTERED: + default: + ShouldNotReachHere(); // Currently aot-linked classes are not supported for this category. + break; + } +} + +void AOTLinkedClassBulkLoader::load_classes_impl(AOTLinkedClassCategory class_category, Array* classes, + const char* category_name, Handle loader, TRAPS) { + if (classes == nullptr) { + return; + } + + ClassLoaderData* loader_data = ClassLoaderData::class_loader_data(loader()); + + for (int i = 0; i < classes->length(); i++) { + InstanceKlass* ik = classes->at(i); + if (log_is_enabled(Info, cds, aot, load)) { + ResourceMark rm(THREAD); + log_info(cds, aot, load)("%-5s %s%s%s", category_name, ik->external_name(), + ik->is_loaded() ? " (already loaded)" : "", + ik->is_hidden() ? " (hidden)" : ""); + } + + if (!ik->is_loaded()) { + if (ik->is_hidden()) { + load_hidden_class(loader_data, ik, CHECK); + } else { + InstanceKlass* actual; + if (loader_data == ClassLoaderData::the_null_class_loader_data()) { + actual = SystemDictionary::load_instance_class(ik->name(), loader, CHECK); + } else { + actual = SystemDictionaryShared::find_or_load_shared_class(ik->name(), loader, CHECK); + } + + if (actual != ik) { + ResourceMark rm(THREAD); + log_error(cds)("Unable to resolve %s class from CDS archive: %s", category_name, ik->external_name()); + log_error(cds)("Expected: " INTPTR_FORMAT ", actual: " INTPTR_FORMAT, p2i(ik), p2i(actual)); + log_error(cds)("JVMTI class retransformation is not supported when archive was generated with -XX:+AOTClassLinking."); + MetaspaceShared::unrecoverable_loading_error(); + } + assert(actual->is_loaded(), "must be"); + } + } + } +} + +// Initiate loading of the in the . The should have already been loaded +// by a parent loader of the . This is necessary for handling pre-resolved CP entries. +// +// For example, we initiate the loading of java/lang/String in the AppClassLoader. This will allow +// any App classes to have a pre-resolved ConstantPool entry that references java/lang/String. +// +// TODO: we can limit the number of initiated classes to only those that are actually referenced by +// AOT-linked classes loaded by . +void AOTLinkedClassBulkLoader::initiate_loading(JavaThread* current, const char* category_name, + Handle initiating_loader, Array* classes) { + if (classes == nullptr) { + return; + } + + assert(initiating_loader() == SystemDictionary::java_platform_loader() || + initiating_loader() == SystemDictionary::java_system_loader(), "must be"); + ClassLoaderData* loader_data = ClassLoaderData::class_loader_data(initiating_loader()); + MonitorLocker mu1(SystemDictionary_lock); + + for (int i = 0; i < classes->length(); i++) { + InstanceKlass* ik = classes->at(i); + assert(ik->is_loaded(), "must have already been loaded by a parent loader"); + assert(ik->class_loader() != initiating_loader(), "must be a parent loader"); + assert(ik->class_loader() == nullptr || + ik->class_loader() == SystemDictionary::java_platform_loader(), "must be"); + if (ik->is_public() && !ik->is_hidden()) { + if (log_is_enabled(Info, cds, aot, load)) { + ResourceMark rm(current); + const char* defining_loader = (ik->class_loader() == nullptr ? "boot" : "plat"); + log_info(cds, aot, load)("%s %s (initiated, defined by %s)", category_name, ik->external_name(), + defining_loader); + } + SystemDictionary::add_to_initiating_loader(current, ik, loader_data); + } + } +} + +// Currently, we archive only three types of hidden classes: +// - LambdaForms +// - lambda proxy classes +// - StringConcat classes +// See HeapShared::is_archivable_hidden_klass(). +// +// LambdaForm classes (with names like java/lang/invoke/LambdaForm$MH+0x800000015) logically +// belong to the boot loader, but they are usually stored in their own special ClassLoaderData to +// facilitate class unloading, as a LambdaForm may refer to a class loaded by a custom loader +// that may be unloaded. +// +// We only support AOT-resolution of indys in the boot/platform/app loader, so there's no need +// to support class unloading. For simplicity, we put all archived LambdaForm classes in the +// "main" ClassLoaderData of the boot loader. +// +// (Even if we were to support other loaders, we would still feel free to ignore any requirement +// of class unloading, for any class asset in the AOT cache. Anything that makes it into the AOT +// cache has a lifetime dispensation from unloading. After all, the AOT cache never grows, and +// we can assume that the user is content with its size, and doesn't need its footprint to shrink.) +// +// Lambda proxy classes are normally stored in the same ClassLoaderData as their nest hosts, and +// StringConcat are normally stored in the main ClassLoaderData of the boot class loader. We +// do the same for the archived copies of such classes. +void AOTLinkedClassBulkLoader::load_hidden_class(ClassLoaderData* loader_data, InstanceKlass* ik, TRAPS) { + assert(HeapShared::is_lambda_form_klass(ik) || + HeapShared::is_lambda_proxy_klass(ik) || + HeapShared::is_string_concat_klass(ik), "sanity"); + DEBUG_ONLY({ + assert(ik->java_super()->is_loaded(), "must be"); + for (int i = 0; i < ik->local_interfaces()->length(); i++) { + assert(ik->local_interfaces()->at(i)->is_loaded(), "must be"); + } + }); + + Handle pd; + PackageEntry* pkg_entry = nullptr; + + // Since a hidden class does not have a name, it cannot be reloaded + // normally via the system dictionary. Instead, we have to finish the + // loading job here. + + if (HeapShared::is_lambda_proxy_klass(ik)) { + InstanceKlass* nest_host = ik->nest_host_not_null(); + assert(nest_host->is_loaded(), "must be"); + pd = Handle(THREAD, nest_host->protection_domain()); + pkg_entry = nest_host->package(); + } + + ik->restore_unshareable_info(loader_data, pd, pkg_entry, CHECK); + SystemDictionary::load_shared_class_misc(ik, loader_data); + ik->add_to_hierarchy(THREAD); + assert(ik->is_loaded(), "Must be in at least loaded state"); + + DEBUG_ONLY({ + // Make sure we don't make this hidden class available by name, even if we don't + // use any special ClassLoaderData. + Handle loader(THREAD, loader_data->class_loader()); + ResourceMark rm(THREAD); + assert(SystemDictionary::resolve_or_null(ik->name(), loader, pd, THREAD) == nullptr, + "hidden classes cannot be accessible by name: %s", ik->external_name()); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; + } + }); +} + +void AOTLinkedClassBulkLoader::finish_loading_javabase_classes(TRAPS) { + init_required_classes_for_loader(Handle(), AOTLinkedClassTable::for_static_archive()->boot(), CHECK); +} + +// Some AOT-linked classes for must be initialized early. This includes +// - classes that were AOT-initialized by AOTClassInitializer +// - the classes of all objects that are reachable from the archived mirrors of +// the AOT-linked classes for . +void AOTLinkedClassBulkLoader::init_required_classes_for_loader(Handle class_loader, Array* classes, TRAPS) { + if (classes != nullptr) { + for (int i = 0; i < classes->length(); i++) { + InstanceKlass* ik = classes->at(i); + if (ik->class_loader_data() == nullptr) { + // This class is not yet loaded. We will initialize it in a later phase. + // For example, we have loaded only AOTLinkedClassCategory::BOOT1 classes + // but k is part of AOTLinkedClassCategory::BOOT2. + continue; + } + if (ik->has_aot_initialized_mirror()) { + ik->initialize_with_aot_initialized_mirror(CHECK); + } else { + // Some cached heap objects may hold references to methods in aot-linked + // classes (via MemberName). We need to make sure all classes are + // linked to allow such MemberNames to be invoked. + ik->link_class(CHECK); + } + } + } + + HeapShared::init_classes_for_special_subgraph(class_loader, CHECK); +} + +bool AOTLinkedClassBulkLoader::is_pending_aot_linked_class(Klass* k) { + if (!CDSConfig::is_using_aot_linked_classes()) { + return false; + } + + if (_all_completed) { // no more pending aot-linked classes + return false; + } + + if (k->is_objArray_klass()) { + k = ObjArrayKlass::cast(k)->bottom_klass(); + } + if (!k->is_instance_klass()) { + // type array klasses (and their higher dimensions), + // must have been loaded before a GC can ever happen. + return false; + } + + // There's a small window during VM start-up where a not-yet loaded aot-linked + // class k may be discovered by the GC during VM initialization. This can happen + // when the heap contains an aot-cached instance of k, but k is not ready to be + // loaded yet. (TODO: JDK-8342429 eliminates this possibility) + // + // The following checks try to limit this window as much as possible for each of + // the four AOTLinkedClassCategory of classes that can be aot-linked. + + InstanceKlass* ik = InstanceKlass::cast(k); + if (ik->is_shared_boot_class()) { + if (ik->module() != nullptr && ik->in_javabase_module()) { + // AOTLinkedClassCategory::BOOT1 -- all aot-linked classes in + // java.base must have been loaded before a GC can ever happen. + return false; + } else { + // AOTLinkedClassCategory::BOOT2 classes cannot be loaded until + // module system is ready. + return !_boot2_completed; + } + } else if (ik->is_shared_platform_class()) { + // AOTLinkedClassCategory::PLATFORM classes cannot be loaded until + // the platform class loader is initialized. + return !_platform_completed; + } else if (ik->is_shared_app_class()) { + // AOTLinkedClassCategory::APP cannot be loaded until the app class loader + // is initialized. + return !_app_completed; + } else { + return false; + } +} diff --git a/src/hotspot/share/cds/aotLinkedClassBulkLoader.hpp b/src/hotspot/share/cds/aotLinkedClassBulkLoader.hpp new file mode 100644 index 00000000000..a8e6365b899 --- /dev/null +++ b/src/hotspot/share/cds/aotLinkedClassBulkLoader.hpp @@ -0,0 +1,69 @@ +/* + * 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. + * + */ + +#ifndef SHARE_CDS_AOTLINKEDCLASSBULKLOADER_HPP +#define SHARE_CDS_AOTLINKEDCLASSBULKLOADER_HPP + +#include "memory/allStatic.hpp" +#include "memory/allocation.hpp" +#include "runtime/handles.hpp" +#include "utilities/exceptions.hpp" +#include "utilities/macros.hpp" + +class AOTLinkedClassTable; +class ClassLoaderData; +class InstanceKlass; +class SerializeClosure; +template class Array; +enum class AOTLinkedClassCategory : int; + +// During a Production Run, the AOTLinkedClassBulkLoader loads all classes from +// a AOTLinkedClassTable into their respective ClassLoaders. This happens very early +// in the JVM bootstrap stage, before any application code is executed. +// +class AOTLinkedClassBulkLoader : AllStatic { + static bool _boot2_completed; + static bool _platform_completed; + static bool _app_completed; + static bool _all_completed; + static void load_classes_in_loader(JavaThread* current, AOTLinkedClassCategory class_category, oop class_loader_oop); + static void load_classes_in_loader_impl(AOTLinkedClassCategory class_category, oop class_loader_oop, TRAPS); + static void load_table(AOTLinkedClassTable* table, AOTLinkedClassCategory class_category, Handle loader, TRAPS); + static void initiate_loading(JavaThread* current, const char* category, Handle initiating_loader, Array* classes); + static void load_classes_impl(AOTLinkedClassCategory class_category, Array* classes, + const char* category_name, Handle loader, TRAPS); + static void load_hidden_class(ClassLoaderData* loader_data, InstanceKlass* ik, TRAPS); + static void init_required_classes_for_loader(Handle class_loader, Array* classes, TRAPS); +public: + static void serialize(SerializeClosure* soc, bool is_static_archive) NOT_CDS_RETURN; + + static void load_javabase_classes(JavaThread* current) NOT_CDS_RETURN; + static void load_non_javabase_classes(JavaThread* current) NOT_CDS_RETURN; + static void finish_loading_javabase_classes(TRAPS) NOT_CDS_RETURN; + static void exit_on_exception(JavaThread* current); + + static bool is_pending_aot_linked_class(Klass* k) NOT_CDS_RETURN_(false); +}; + +#endif // SHARE_CDS_AOTLINKEDCLASSBULKLOADER_HPP diff --git a/src/hotspot/share/cds/aotLinkedClassTable.cpp b/src/hotspot/share/cds/aotLinkedClassTable.cpp new file mode 100644 index 00000000000..bed090f00a9 --- /dev/null +++ b/src/hotspot/share/cds/aotLinkedClassTable.cpp @@ -0,0 +1,40 @@ +/* + * 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. + * + */ + +#include "precompiled.hpp" +#include "cds/aotLinkedClassTable.hpp" +#include "cds/cdsConfig.hpp" +#include "cds/serializeClosure.hpp" +#include "oops/array.hpp" + +AOTLinkedClassTable AOTLinkedClassTable::_for_static_archive; +AOTLinkedClassTable AOTLinkedClassTable::_for_dynamic_archive; + +void AOTLinkedClassTable::serialize(SerializeClosure* soc) { + soc->do_ptr((void**)&_boot); + soc->do_ptr((void**)&_boot2); + soc->do_ptr((void**)&_platform); + soc->do_ptr((void**)&_app); +} + diff --git a/src/hotspot/share/cds/aotLinkedClassTable.hpp b/src/hotspot/share/cds/aotLinkedClassTable.hpp new file mode 100644 index 00000000000..2a199c15edd --- /dev/null +++ b/src/hotspot/share/cds/aotLinkedClassTable.hpp @@ -0,0 +1,77 @@ +/* + * 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. + * + */ + +#ifndef SHARE_CDS_AOTLINKEDCLASSTABLE_HPP +#define SHARE_CDS_AOTLINKEDCLASSTABLE_HPP + +#include "utilities/globalDefinitions.hpp" + +template class Array; +class InstanceKlass; +class SerializeClosure; + +// Classes to be bulk-loaded, in the "linked" state, at VM bootstrap. +// +// AOTLinkedClassTable is produced by AOTClassLinker when an AOTCache is assembled. +// +// AOTLinkedClassTable is consumed by AOTLinkedClassBulkLoader when an AOTCache is used +// in a production run. +// +class AOTLinkedClassTable { + // The VM may load up to 2 CDS archives -- static and dynamic. Each + // archive can have its own AOTLinkedClassTable. + static AOTLinkedClassTable _for_static_archive; + static AOTLinkedClassTable _for_dynamic_archive; + + Array* _boot; // only java.base classes + Array* _boot2; // boot classes in other modules + Array* _platform; + Array* _app; + +public: + AOTLinkedClassTable() : + _boot(nullptr), _boot2(nullptr), + _platform(nullptr), _app(nullptr) {} + + static AOTLinkedClassTable* for_static_archive() { return &_for_static_archive; } + static AOTLinkedClassTable* for_dynamic_archive() { return &_for_dynamic_archive; } + + static AOTLinkedClassTable* get(bool is_static_archive) { + return is_static_archive ? for_static_archive() : for_dynamic_archive(); + } + + Array* boot() const { return _boot; } + Array* boot2() const { return _boot2; } + Array* platform() const { return _platform; } + Array* app() const { return _app; } + + void set_boot (Array* value) { _boot = value; } + void set_boot2 (Array* value) { _boot2 = value; } + void set_platform(Array* value) { _platform = value; } + void set_app (Array* value) { _app = value; } + + void serialize(SerializeClosure* soc); +}; + +#endif // SHARE_CDS_AOTLINKEDCLASSTABLE_HPP diff --git a/src/hotspot/share/cds/archiveBuilder.cpp b/src/hotspot/share/cds/archiveBuilder.cpp index a181257a519..da4319955ea 100644 --- a/src/hotspot/share/cds/archiveBuilder.cpp +++ b/src/hotspot/share/cds/archiveBuilder.cpp @@ -23,6 +23,8 @@ */ #include "precompiled.hpp" +#include "cds/aotClassLinker.hpp" +#include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveHeapWriter.hpp" #include "cds/archiveUtils.hpp" @@ -33,7 +35,9 @@ #include "cds/heapShared.hpp" #include "cds/metaspaceShared.hpp" #include "cds/regeneratedClasses.hpp" +#include "classfile/classLoader.hpp" #include "classfile/classLoaderDataShared.hpp" +#include "classfile/classLoaderExt.hpp" #include "classfile/javaClasses.hpp" #include "classfile/symbolTable.hpp" #include "classfile/systemDictionaryShared.hpp" @@ -226,6 +230,10 @@ bool ArchiveBuilder::gather_klass_and_symbol(MetaspaceClosure::Ref* ref, bool re assert(klass->is_klass(), "must be"); if (!is_excluded(klass)) { _klasses->append(klass); + if (klass->is_hidden()) { + assert(klass->is_instance_klass(), "must be"); + assert(SystemDictionaryShared::should_hidden_class_be_archived(InstanceKlass::cast(klass)), "must be"); + } } // See RunTimeClassInfo::get_for(): make sure we have enough space for both maximum // Klass alignment as well as the RuntimeInfo* pointer we will embed in front of a Klass. @@ -284,6 +292,8 @@ void ArchiveBuilder::gather_klasses_and_symbols() { // but this should be enough for now _estimated_metaspaceobj_bytes += 200 * 1024 * 1024; } + + AOTClassLinker::add_candidates(); } int ArchiveBuilder::compare_symbols_by_address(Symbol** a, Symbol** b) { @@ -310,6 +320,15 @@ size_t ArchiveBuilder::estimate_archive_size() { size_t dictionary_est = SystemDictionaryShared::estimate_size_for_archive(); _estimated_hashtable_bytes = symbol_table_est + dictionary_est; + if (CDSConfig::is_dumping_aot_linked_classes()) { + // This is difficult to estimate when dumping the dynamic archive, as the + // AOTLinkedClassTable may need to contain classes in the static archive as well. + // + // Just give a generous estimate for now. We will remove estimate_archive_size() + // in JDK-8340416 + _estimated_hashtable_bytes += 20 * 1024 * 1024; + } + size_t total = 0; total += _estimated_metaspaceobj_bytes; @@ -423,11 +442,12 @@ bool ArchiveBuilder::gather_one_source_obj(MetaspaceClosure::Ref* ref, bool read if (src_obj == nullptr) { return false; } + + remember_embedded_pointer_in_enclosing_obj(ref); if (RegeneratedClasses::has_been_regenerated(src_obj)) { // No need to copy it. We will later relocate it to point to the regenerated klass/method. return false; } - remember_embedded_pointer_in_enclosing_obj(ref); FollowMode follow_mode = get_follow_mode(ref); SourceObjInfo src_info(ref, read_only, follow_mode); @@ -740,6 +760,16 @@ void ArchiveBuilder::mark_and_relocate_to_buffered_addr(address* ptr_location) { ArchivePtrMarker::mark_pointer(ptr_location); } +bool ArchiveBuilder::has_been_buffered(address src_addr) const { + if (RegeneratedClasses::has_been_regenerated(src_addr) || + _src_obj_table.get(src_addr) == nullptr || + get_buffered_addr(src_addr) == nullptr) { + return false; + } else { + return true; + } +} + 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", @@ -767,17 +797,35 @@ void ArchiveBuilder::relocate_metaspaceobj_embedded_pointers() { relocate_embedded_pointers(&_ro_src_objs); } +#define ADD_COUNT(x) \ + x += 1; \ + x ## _a += aotlinked ? 1 : 0; \ + x ## _i += inited ? 1 : 0; + +#define DECLARE_INSTANCE_KLASS_COUNTER(x) \ + int x = 0; \ + int x ## _a = 0; \ + int x ## _i = 0; + void ArchiveBuilder::make_klasses_shareable() { - int num_instance_klasses = 0; - int num_boot_klasses = 0; - int num_platform_klasses = 0; - int num_app_klasses = 0; - int num_hidden_klasses = 0; + DECLARE_INSTANCE_KLASS_COUNTER(num_instance_klasses); + DECLARE_INSTANCE_KLASS_COUNTER(num_boot_klasses); + DECLARE_INSTANCE_KLASS_COUNTER(num_vm_klasses); + DECLARE_INSTANCE_KLASS_COUNTER(num_platform_klasses); + DECLARE_INSTANCE_KLASS_COUNTER(num_app_klasses); + DECLARE_INSTANCE_KLASS_COUNTER(num_old_klasses); + DECLARE_INSTANCE_KLASS_COUNTER(num_hidden_klasses); + DECLARE_INSTANCE_KLASS_COUNTER(num_enum_klasses); + DECLARE_INSTANCE_KLASS_COUNTER(num_unregistered_klasses); int num_unlinked_klasses = 0; - int num_unregistered_klasses = 0; int num_obj_array_klasses = 0; int num_type_array_klasses = 0; + int boot_unlinked = 0; + int platform_unlinked = 0; + int app_unlinked = 0; + int unreg_unlinked = 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 @@ -791,8 +839,12 @@ void ArchiveBuilder::make_klasses_shareable() { for (int i = 0; i < klasses()->length(); i++) { const char* type; const char* unlinked = ""; + const char* kind = ""; const char* hidden = ""; + const char* old = ""; const char* generated = ""; + const char* aotlinked_msg = ""; + const char* inited_msg = ""; Klass* k = get_buffered_addr(klasses()->at(i)); k->remove_java_mirror(); #ifdef _LP64 @@ -815,60 +867,127 @@ void ArchiveBuilder::make_klasses_shareable() { k->remove_unshareable_info(); } else { assert(k->is_instance_klass(), " must be"); - num_instance_klasses ++; InstanceKlass* ik = InstanceKlass::cast(k); - if (ik->is_shared_boot_class()) { + InstanceKlass* src_ik = get_source_addr(ik); + bool aotlinked = AOTClassLinker::is_candidate(src_ik); + bool inited = ik->has_aot_initialized_mirror(); + ADD_COUNT(num_instance_klasses); + if (CDSConfig::is_dumping_dynamic_archive()) { + // For static dump, class loader type are already set. + ik->assign_class_loader_type(); + } + if (ik->is_hidden()) { + ADD_COUNT(num_hidden_klasses); + hidden = " hidden"; + oop loader = k->class_loader(); + if (loader == nullptr) { + type = "boot"; + ADD_COUNT(num_boot_klasses); + } else if (loader == SystemDictionary::java_platform_loader()) { + type = "plat"; + ADD_COUNT(num_platform_klasses); + } else if (loader == SystemDictionary::java_system_loader()) { + type = "app"; + ADD_COUNT(num_app_klasses); + } else { + type = "bad"; + assert(0, "shouldn't happen"); + } + if (CDSConfig::is_dumping_invokedynamic()) { + assert(HeapShared::is_archivable_hidden_klass(ik), "sanity"); + } else { + // Legacy CDS support for lambda proxies + assert(HeapShared::is_lambda_proxy_klass(ik), "sanity"); + } + } else if (ik->is_shared_boot_class()) { type = "boot"; - num_boot_klasses ++; + ADD_COUNT(num_boot_klasses); } else if (ik->is_shared_platform_class()) { type = "plat"; - num_platform_klasses ++; + ADD_COUNT(num_platform_klasses); } else if (ik->is_shared_app_class()) { type = "app"; - num_app_klasses ++; + ADD_COUNT(num_app_klasses); } else { assert(ik->is_shared_unregistered_class(), "must be"); type = "unreg"; - num_unregistered_klasses ++; + ADD_COUNT(num_unregistered_klasses); + } + + if (AOTClassLinker::is_vm_class(src_ik)) { + ADD_COUNT(num_vm_klasses); } if (!ik->is_linked()) { num_unlinked_klasses ++; - unlinked = " ** unlinked"; + unlinked = " unlinked"; + if (ik->is_shared_boot_class()) { + boot_unlinked ++; + } else if (ik->is_shared_platform_class()) { + platform_unlinked ++; + } else if (ik->is_shared_app_class()) { + app_unlinked ++; + } else { + unreg_unlinked ++; + } } - if (ik->is_hidden()) { - num_hidden_klasses ++; - hidden = " ** hidden"; + if (ik->is_interface()) { + kind = " interface"; + } else if (src_ik->is_enum_subclass()) { + kind = " enum"; + ADD_COUNT(num_enum_klasses); + } + + if (!ik->can_be_verified_at_dumptime()) { + ADD_COUNT(num_old_klasses); + old = " old"; } if (ik->is_generated_shared_class()) { - generated = " ** generated"; + generated = " generated"; } + if (aotlinked) { + aotlinked_msg = " aot-linked"; + } + if (inited) { + inited_msg = " inited"; + } + MetaspaceShared::rewrite_nofast_bytecodes_and_calculate_fingerprints(Thread::current(), ik); ik->remove_unshareable_info(); } if (log_is_enabled(Debug, cds, class)) { ResourceMark rm; - log_debug(cds, class)("klasses[%5d] = " PTR_FORMAT " %-5s %s%s%s%s", i, + log_debug(cds, class)("klasses[%5d] = " PTR_FORMAT " %-5s %s%s%s%s%s%s%s%s", i, p2i(to_requested(k)), type, k->external_name(), - hidden, unlinked, generated); + kind, hidden, old, unlinked, generated, aotlinked_msg, inited_msg); } } +#define STATS_FORMAT "= %5d, aot-linked = %5d, inited = %5d" +#define STATS_PARAMS(x) num_ ## x, num_ ## x ## _a, num_ ## x ## _i + log_info(cds)("Number of classes %d", num_instance_klasses + num_obj_array_klasses + num_type_array_klasses); - log_info(cds)(" instance classes = %5d", num_instance_klasses); - log_info(cds)(" boot = %5d", num_boot_klasses); - log_info(cds)(" app = %5d", num_app_klasses); - log_info(cds)(" platform = %5d", num_platform_klasses); - log_info(cds)(" unregistered = %5d", num_unregistered_klasses); - log_info(cds)(" (hidden) = %5d", num_hidden_klasses); - log_info(cds)(" (unlinked) = %5d", num_unlinked_klasses); + log_info(cds)(" instance classes " STATS_FORMAT, STATS_PARAMS(instance_klasses)); + log_info(cds)(" boot " STATS_FORMAT, STATS_PARAMS(boot_klasses)); + log_info(cds)(" vm " STATS_FORMAT, STATS_PARAMS(vm_klasses)); + log_info(cds)(" platform " STATS_FORMAT, STATS_PARAMS(platform_klasses)); + log_info(cds)(" app " STATS_FORMAT, STATS_PARAMS(app_klasses)); + log_info(cds)(" unregistered " STATS_FORMAT, STATS_PARAMS(unregistered_klasses)); + log_info(cds)(" (enum) " STATS_FORMAT, STATS_PARAMS(enum_klasses)); + log_info(cds)(" (hidden) " STATS_FORMAT, STATS_PARAMS(hidden_klasses)); + log_info(cds)(" (old) " STATS_FORMAT, STATS_PARAMS(old_klasses)); + log_info(cds)(" (unlinked) = %5d, boot = %d, plat = %d, app = %d, unreg = %d", + num_unlinked_klasses, boot_unlinked, platform_unlinked, app_unlinked, unreg_unlinked); log_info(cds)(" obj array classes = %5d", num_obj_array_klasses); log_info(cds)(" type array classes = %5d", num_type_array_klasses); log_info(cds)(" symbols = %5d", _symbols->length()); +#undef STATS_FORMAT +#undef STATS_PARAMS + DynamicArchive::make_array_klasses_shareable(); } @@ -876,6 +995,7 @@ void ArchiveBuilder::serialize_dynamic_archivable_items(SerializeClosure* soc) { SymbolTable::serialize_shared_table_header(soc, false); SystemDictionaryShared::serialize_dictionary_headers(soc, false); DynamicArchive::serialize_array_klasses(soc); + AOTLinkedClassBulkLoader::serialize(soc, false); } uintx ArchiveBuilder::buffer_to_offset(address p) const { diff --git a/src/hotspot/share/cds/archiveBuilder.hpp b/src/hotspot/share/cds/archiveBuilder.hpp index f306e4676b3..39fffc26c37 100644 --- a/src/hotspot/share/cds/archiveBuilder.hpp +++ b/src/hotspot/share/cds/archiveBuilder.hpp @@ -292,7 +292,7 @@ public: intx buffer_to_requested_delta() const { return _buffer_to_requested_delta; } bool is_in_buffer_space(address p) const { - return (buffer_bottom() <= p && p < buffer_top()); + return (buffer_bottom() != nullptr && buffer_bottom() <= p && p < buffer_top()); } template bool is_in_requested_static_archive(T p) const { @@ -420,6 +420,11 @@ public: mark_and_relocate_to_buffered_addr((address*)ptr_location); } + bool has_been_buffered(address src_addr) const; + template bool has_been_buffered(T src_addr) const { + return has_been_buffered((address)src_addr); + } + address get_buffered_addr(address src_addr) const; template T get_buffered_addr(T src_addr) const { return (T)get_buffered_addr((address)src_addr); diff --git a/src/hotspot/share/cds/archiveHeapLoader.cpp b/src/hotspot/share/cds/archiveHeapLoader.cpp index 01831cf0f3e..b05fd20f4f5 100644 --- a/src/hotspot/share/cds/archiveHeapLoader.cpp +++ b/src/hotspot/share/cds/archiveHeapLoader.cpp @@ -449,10 +449,6 @@ class PatchNativePointers: public BitMapClosure { bool do_bit(size_t offset) { Metadata** p = _start + offset; *p = (Metadata*)(address(*p) + MetaspaceShared::relocation_delta()); - // Currently we have only Klass pointers in heap objects. - // This needs to be relaxed when we support other types of native - // pointers such as Method. - assert(((Klass*)(*p))->is_klass(), "must be"); return true; } }; diff --git a/src/hotspot/share/cds/archiveHeapWriter.cpp b/src/hotspot/share/cds/archiveHeapWriter.cpp index 636b368117d..be821044a96 100644 --- a/src/hotspot/share/cds/archiveHeapWriter.cpp +++ b/src/hotspot/share/cds/archiveHeapWriter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 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 @@ -27,14 +27,15 @@ #include "cds/cdsConfig.hpp" #include "cds/filemap.hpp" #include "cds/heapShared.hpp" +#include "classfile/javaClasses.hpp" #include "classfile/systemDictionary.hpp" #include "gc/shared/collectedHeap.hpp" #include "memory/iterator.inline.hpp" #include "memory/oopFactory.hpp" #include "memory/universe.hpp" #include "oops/compressedOops.hpp" -#include "oops/oop.inline.hpp" #include "oops/objArrayOop.inline.hpp" +#include "oops/oop.inline.hpp" #include "oops/oopHandle.inline.hpp" #include "oops/typeArrayKlass.hpp" #include "oops/typeArrayOop.hpp" @@ -535,7 +536,14 @@ oop ArchiveHeapWriter::load_oop_from_buffer(narrowOop* buffered_addr) { template void ArchiveHeapWriter::relocate_field_in_buffer(T* field_addr_in_buffer, CHeapBitMap* oopmap) { oop source_referent = load_source_oop_from_buffer(field_addr_in_buffer); - if (!CompressedOops::is_null(source_referent)) { + if (source_referent != nullptr) { + if (java_lang_Class::is_instance(source_referent)) { + // When the source object points to a "real" mirror, the buffered object should point + // to the "scratch" mirror, which has all unarchivable fields scrubbed (to be reinstated + // at run time). + source_referent = HeapShared::scratch_java_mirror(source_referent); + assert(source_referent != nullptr, "must be"); + } oop request_referent = source_obj_to_requested_obj(source_referent); store_requested_oop_in_buffer(field_addr_in_buffer, request_referent); mark_oop_pointer(field_addr_in_buffer, oopmap); @@ -731,7 +739,9 @@ void ArchiveHeapWriter::compute_ptrmap(ArchiveHeapInfo* heap_info) { Metadata** buffered_field_addr = requested_addr_to_buffered_addr(requested_field_addr); Metadata* native_ptr = *buffered_field_addr; - assert(native_ptr != nullptr, "sanity"); + guarantee(native_ptr != nullptr, "sanity"); + guarantee(ArchiveBuilder::current()->has_been_buffered((address)native_ptr), + "Metadata %p should have been archived", native_ptr); address buffered_native_ptr = ArchiveBuilder::current()->get_buffered_addr((address)native_ptr); address requested_native_ptr = ArchiveBuilder::current()->to_requested(buffered_native_ptr); diff --git a/src/hotspot/share/cds/archiveUtils.cpp b/src/hotspot/share/cds/archiveUtils.cpp index c7282f7d97c..4d717879e0f 100644 --- a/src/hotspot/share/cds/archiveUtils.cpp +++ b/src/hotspot/share/cds/archiveUtils.cpp @@ -39,6 +39,7 @@ #include "memory/metaspaceUtils.hpp" #include "memory/resourceArea.hpp" #include "oops/compressedOops.inline.hpp" +#include "oops/klass.inline.hpp" #include "runtime/arguments.hpp" #include "utilities/bitMap.inline.hpp" #include "utilities/debug.hpp" @@ -370,6 +371,14 @@ void ArchiveUtils::log_to_classlist(BootstrapInfo* bootstrap_specifier, TRAPS) { } } +bool ArchiveUtils::has_aot_initialized_mirror(InstanceKlass* src_ik) { + if (SystemDictionaryShared::is_excluded_class(src_ik)) { + assert(!ArchiveBuilder::current()->has_been_buffered(src_ik), "sanity"); + return false; + } + return ArchiveBuilder::current()->get_buffered_addr(src_ik)->has_aot_initialized_mirror(); +} + size_t HeapRootSegments::size_in_bytes(size_t seg_idx) { assert(seg_idx < _count, "In range"); return objArrayOopDesc::object_size(size_in_elems(seg_idx)) * HeapWordSize; diff --git a/src/hotspot/share/cds/archiveUtils.hpp b/src/hotspot/share/cds/archiveUtils.hpp index 7fb8e538084..606f5137e9d 100644 --- a/src/hotspot/share/cds/archiveUtils.hpp +++ b/src/hotspot/share/cds/archiveUtils.hpp @@ -38,6 +38,9 @@ class BootstrapInfo; class ReservedSpace; class VirtualSpace; +template class Array; +template class GrowableArray; + // ArchivePtrMarker is used to mark the location of pointers embedded in a CDS archive. E.g., when an // InstanceKlass k is dumped, we mark the location of the k->_name pointer by effectively calling // mark_pointer(/*ptr_loc=*/&k->_name). It's required that (_prt_base <= ptr_loc < _ptr_end). _ptr_base is @@ -253,6 +256,8 @@ class ArchiveUtils { public: static const uintx MAX_SHARED_DELTA = 0x7FFFFFFF; static void log_to_classlist(BootstrapInfo* bootstrap_specifier, TRAPS) NOT_CDS_RETURN; + static bool has_aot_initialized_mirror(InstanceKlass* src_ik); + template static Array* archive_array(GrowableArray* tmp_array); // offset must represent an object of type T in the mapped shared space. Return // a direct pointer to this object. diff --git a/src/hotspot/share/cds/archiveUtils.inline.hpp b/src/hotspot/share/cds/archiveUtils.inline.hpp index 7df344e3701..537b3d1670c 100644 --- a/src/hotspot/share/cds/archiveUtils.inline.hpp +++ b/src/hotspot/share/cds/archiveUtils.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -27,7 +27,10 @@ #include "cds/archiveUtils.hpp" +#include "cds/archiveBuilder.hpp" +#include "oops/array.hpp" #include "utilities/bitMap.inline.hpp" +#include "utilities/growableArray.hpp" inline bool SharedDataRelocator::do_bit(size_t offset) { address* p = _patch_base + offset; @@ -47,4 +50,19 @@ inline bool SharedDataRelocator::do_bit(size_t offset) { return true; // keep iterating } +// Returns the address of an Array that's allocated in the ArchiveBuilder "buffer" space. +template +Array* ArchiveUtils::archive_array(GrowableArray* tmp_array) { + Array* archived_array = ArchiveBuilder::new_ro_array(tmp_array->length()); + for (int i = 0; i < tmp_array->length(); i++) { + archived_array->at_put(i, tmp_array->at(i)); + if (std::is_pointer::value) { + ArchivePtrMarker::mark_pointer(archived_array->adr_at(i)); + } + } + + return archived_array; +} + + #endif // SHARE_CDS_ARCHIVEUTILS_INLINE_HPP diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index 691a1983732..ba5d12e6450 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -33,19 +33,27 @@ #include "logging/log.hpp" #include "memory/universe.hpp" #include "runtime/arguments.hpp" +#include "runtime/globals_extension.hpp" #include "runtime/java.hpp" +#include "runtime/vmThread.hpp" #include "utilities/defaultStream.hpp" +#include "utilities/formatBuffer.hpp" bool CDSConfig::_is_dumping_static_archive = false; bool CDSConfig::_is_dumping_dynamic_archive = false; bool CDSConfig::_is_using_optimized_module_handling = true; bool CDSConfig::_is_dumping_full_module_graph = true; bool CDSConfig::_is_using_full_module_graph = true; +bool CDSConfig::_has_aot_linked_classes = false; +bool CDSConfig::_has_archived_invokedynamic = false; +bool CDSConfig::_old_cds_flags_used = false; char* CDSConfig::_default_archive_path = nullptr; char* CDSConfig::_static_archive_path = nullptr; char* CDSConfig::_dynamic_archive_path = nullptr; +JavaThread* CDSConfig::_dumper_thread = nullptr; + int CDSConfig::get_status() { assert(Universe::is_fully_initialized(), "status is finalized only after Universe is initialized"); return (is_dumping_archive() ? IS_DUMPING_ARCHIVE : 0) | @@ -54,7 +62,6 @@ int CDSConfig::get_status() { (is_using_archive() ? IS_USING_ARCHIVE : 0); } - void CDSConfig::initialize() { if (is_dumping_static_archive()) { if (RequireSharedSpaces) { @@ -334,7 +341,95 @@ bool CDSConfig::has_unsupported_runtime_module_options() { return false; } +#define CHECK_ALIAS(f) check_flag_alias(FLAG_IS_DEFAULT(f), #f) + +void CDSConfig::check_flag_alias(bool alias_is_default, const char* alias_name) { + if (_old_cds_flags_used && !alias_is_default) { + vm_exit_during_initialization(err_msg("Option %s cannot be used at the same time with " + "-Xshare:on, -Xshare:auto, -Xshare:off, -Xshare:dump, " + "DumpLoadedClassList, SharedClassListFile, or SharedArchiveFile", + alias_name)); + } +} + +void CDSConfig::check_flag_aliases() { + if (!FLAG_IS_DEFAULT(DumpLoadedClassList) || + !FLAG_IS_DEFAULT(SharedClassListFile) || + !FLAG_IS_DEFAULT(SharedArchiveFile)) { + _old_cds_flags_used = true; + } + + CHECK_ALIAS(AOTCache); + CHECK_ALIAS(AOTConfiguration); + CHECK_ALIAS(AOTMode); + + if (FLAG_IS_DEFAULT(AOTCache) && FLAG_IS_DEFAULT(AOTConfiguration) && FLAG_IS_DEFAULT(AOTMode)) { + // Aliases not used. + return; + } + + if (FLAG_IS_DEFAULT(AOTMode) || strcmp(AOTMode, "auto") == 0 || strcmp(AOTMode, "on") == 0) { + if (!FLAG_IS_DEFAULT(AOTConfiguration)) { + vm_exit_during_initialization("AOTConfiguration can only be used with -XX:AOTMode=record or -XX:AOTMode=create"); + } + + if (!FLAG_IS_DEFAULT(AOTCache)) { + assert(FLAG_IS_DEFAULT(SharedArchiveFile), "already checked"); + FLAG_SET_ERGO(SharedArchiveFile, AOTCache); + } + + UseSharedSpaces = true; + if (FLAG_IS_DEFAULT(AOTMode) || (strcmp(AOTMode, "auto") == 0)) { + RequireSharedSpaces = false; + } else { + assert(strcmp(AOTMode, "on") == 0, "already checked"); + RequireSharedSpaces = true; + } + } else if (strcmp(AOTMode, "off") == 0) { + UseSharedSpaces = false; + RequireSharedSpaces = false; + } else { + // AOTMode is record or create + if (FLAG_IS_DEFAULT(AOTConfiguration)) { + vm_exit_during_initialization(err_msg("-XX:AOTMode=%s cannot be used without setting AOTConfiguration", AOTMode)); + } + + if (strcmp(AOTMode, "record") == 0) { + if (!FLAG_IS_DEFAULT(AOTCache)) { + vm_exit_during_initialization("AOTCache must not be specified when using -XX:AOTMode=record"); + } + + assert(FLAG_IS_DEFAULT(DumpLoadedClassList), "already checked"); + FLAG_SET_ERGO(DumpLoadedClassList, AOTConfiguration); + UseSharedSpaces = false; + RequireSharedSpaces = false; + } else { + assert(strcmp(AOTMode, "create") == 0, "checked by AOTModeConstraintFunc"); + if (FLAG_IS_DEFAULT(AOTCache)) { + vm_exit_during_initialization("AOTCache must be specified when using -XX:AOTMode=create"); + } + + assert(FLAG_IS_DEFAULT(SharedClassListFile), "already checked"); + FLAG_SET_ERGO(SharedClassListFile, AOTConfiguration); + assert(FLAG_IS_DEFAULT(SharedArchiveFile), "already checked"); + FLAG_SET_ERGO(SharedArchiveFile, AOTCache); + + CDSConfig::enable_dumping_static_archive(); + } + } +} + bool CDSConfig::check_vm_args_consistency(bool patch_mod_javabase, bool mode_flag_cmd_line) { + check_flag_aliases(); + + if (AOTClassLinking) { + // If AOTClassLinking is specified, enable all AOT optimizations by default. + FLAG_SET_ERGO_IF_DEFAULT(AOTInvokeDynamicLinking, true); + } else { + // AOTInvokeDynamicLinking depends on AOTClassLinking. + FLAG_SET_ERGO(AOTInvokeDynamicLinking, false); + } + if (is_dumping_static_archive()) { if (!mode_flag_cmd_line) { // By default, -Xshare:dump runs in interpreter-only mode, which is required for deterministic archive. @@ -353,6 +448,9 @@ bool CDSConfig::check_vm_args_consistency(bool patch_mod_javabase, bool mode_fla // run to another which resulting in non-determinstic CDS archives. // Disable UseStringDeduplication while dumping CDS archive. UseStringDeduplication = false; + + // Don't use SoftReferences so that objects used by java.lang.invoke tables can be archived. + Arguments::PropertyList_add(new SystemProperty("java.lang.invoke.MethodHandleNatives.USE_SOFT_CACHE", "false", false)); } // RecordDynamicDumpInfo is not compatible with ArchiveClassesAtExit @@ -397,6 +495,11 @@ bool CDSConfig::check_vm_args_consistency(bool patch_mod_javabase, bool mode_fla return true; } +bool CDSConfig::allow_only_single_java_thread() { + // See comments in JVM_StartThread() + return is_dumping_static_archive(); +} + bool CDSConfig::is_using_archive() { return UseSharedSpaces; } @@ -411,12 +514,32 @@ void CDSConfig::stop_using_optimized_module_handling() { _is_using_full_module_graph = false; // This requires is_using_optimized_module_handling() } + +CDSConfig::DumperThreadMark::DumperThreadMark(JavaThread* current) { + assert(_dumper_thread == nullptr, "sanity"); + _dumper_thread = current; +} + +CDSConfig::DumperThreadMark::~DumperThreadMark() { + assert(_dumper_thread != nullptr, "sanity"); + _dumper_thread = nullptr; +} + +bool CDSConfig::current_thread_is_vm_or_dumper() { + Thread* t = Thread::current(); + return t != nullptr && (t->is_VM_thread() || t == _dumper_thread); +} + #if INCLUDE_CDS_JAVA_HEAP bool CDSConfig::is_dumping_heap() { // heap dump is not supported in dynamic dump return is_dumping_static_archive() && HeapShared::can_write(); } +bool CDSConfig::is_loading_heap() { + return ArchiveHeapLoader::is_in_use(); +} + bool CDSConfig::is_using_full_module_graph() { if (ClassLoaderDataShared::is_full_module_graph_loaded()) { return true; @@ -455,4 +578,39 @@ void CDSConfig::stop_using_full_module_graph(const char* reason) { } } } + +bool CDSConfig::is_dumping_aot_linked_classes() { + if (is_dumping_dynamic_archive()) { + return is_using_full_module_graph() && AOTClassLinking; + } else if (is_dumping_static_archive()) { + return is_dumping_full_module_graph() && AOTClassLinking; + } else { + return false; + } +} + +bool CDSConfig::is_using_aot_linked_classes() { + // Make sure we have the exact same module graph as in the assembly phase, or else + // some aot-linked classes may not be visible so cannot be loaded. + return is_using_full_module_graph() && _has_aot_linked_classes; +} + +void CDSConfig::set_has_aot_linked_classes(bool has_aot_linked_classes) { + _has_aot_linked_classes |= has_aot_linked_classes; +} + +bool CDSConfig::is_initing_classes_at_dump_time() { + return is_dumping_heap() && is_dumping_aot_linked_classes(); +} + +bool CDSConfig::is_dumping_invokedynamic() { + // Requires is_dumping_aot_linked_classes(). Otherwise the classes of some archived heap + // objects used by the archive indy callsites may be replaced at runtime. + return AOTInvokeDynamicLinking && is_dumping_aot_linked_classes() && is_dumping_heap(); +} + +bool CDSConfig::is_loading_invokedynamic() { + return UseSharedSpaces && is_using_full_module_graph() && _has_archived_invokedynamic; +} + #endif // INCLUDE_CDS_JAVA_HEAP diff --git a/src/hotspot/share/cds/cdsConfig.hpp b/src/hotspot/share/cds/cdsConfig.hpp index 7711a2ea1e2..cceaceeb6d7 100644 --- a/src/hotspot/share/cds/cdsConfig.hpp +++ b/src/hotspot/share/cds/cdsConfig.hpp @@ -29,6 +29,8 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" +class JavaThread; + class CDSConfig : public AllStatic { #if INCLUDE_CDS static bool _is_dumping_static_archive; @@ -36,10 +38,16 @@ class CDSConfig : public AllStatic { static bool _is_using_optimized_module_handling; static bool _is_dumping_full_module_graph; static bool _is_using_full_module_graph; + static bool _has_aot_linked_classes; + static bool _has_archived_invokedynamic; static char* _default_archive_path; static char* _static_archive_path; static char* _dynamic_archive_path; + + static bool _old_cds_flags_used; + + static JavaThread* _dumper_thread; #endif static void extract_shared_archive_paths(const char* archive_path, @@ -47,6 +55,9 @@ class CDSConfig : public AllStatic { char** top_archive_path); static void init_shared_archive_paths(); + static void check_flag_alias(bool alias_is_default, const char* alias_name); + static void check_flag_aliases(); + public: // Used by jdk.internal.misc.CDS.getCDSConfigStatus(); static const int IS_DUMPING_ARCHIVE = 1 << 0; @@ -57,6 +68,8 @@ public: // Initialization and command-line checking static void initialize() NOT_CDS_RETURN; + static void set_old_cds_flags_used() { CDS_ONLY(_old_cds_flags_used = true); } + static bool old_cds_flags_used() { return CDS_ONLY(_old_cds_flags_used) NOT_CDS(false); } static void check_internal_module_property(const char* key, const char* value) NOT_CDS_RETURN; static void check_incompatible_property(const char* key, const char* value) NOT_CDS_RETURN; static void check_unsupported_dumping_module_options() NOT_CDS_RETURN; @@ -79,12 +92,19 @@ public: static void enable_dumping_dynamic_archive() { CDS_ONLY(_is_dumping_dynamic_archive = true); } static void disable_dumping_dynamic_archive() { CDS_ONLY(_is_dumping_dynamic_archive = false); } + // Misc CDS features + static bool allow_only_single_java_thread() NOT_CDS_RETURN_(false); + // optimized_module_handling -- can we skip some expensive operations related to modules? static bool is_using_optimized_module_handling() { return CDS_ONLY(_is_using_optimized_module_handling) NOT_CDS(false); } static void stop_using_optimized_module_handling() NOT_CDS_RETURN; static bool is_logging_lambda_form_invokers() NOT_CDS_RETURN_(false); + static bool is_dumping_aot_linked_classes() NOT_CDS_JAVA_HEAP_RETURN_(false); + static bool is_using_aot_linked_classes() NOT_CDS_JAVA_HEAP_RETURN_(false); + static void set_has_aot_linked_classes(bool has_aot_linked_classes) NOT_CDS_JAVA_HEAP_RETURN; + // archive_path // Points to the classes.jsa in $JAVA_HOME @@ -96,13 +116,34 @@ public: // --- Archived java objects - static bool is_dumping_heap() NOT_CDS_JAVA_HEAP_RETURN_(false); + static bool is_dumping_heap() NOT_CDS_JAVA_HEAP_RETURN_(false); + static bool is_loading_heap() NOT_CDS_JAVA_HEAP_RETURN_(false); + static bool is_initing_classes_at_dump_time() NOT_CDS_JAVA_HEAP_RETURN_(false); + + static bool is_dumping_invokedynamic() NOT_CDS_JAVA_HEAP_RETURN_(false); + static bool is_loading_invokedynamic() NOT_CDS_JAVA_HEAP_RETURN_(false); + static void set_has_archived_invokedynamic() { CDS_JAVA_HEAP_ONLY(_has_archived_invokedynamic = true); } // full_module_graph (requires optimized_module_handling) static bool is_dumping_full_module_graph() { return CDS_ONLY(_is_dumping_full_module_graph) NOT_CDS(false); } static bool is_using_full_module_graph() NOT_CDS_JAVA_HEAP_RETURN_(false); static void stop_dumping_full_module_graph(const char* reason = nullptr) NOT_CDS_JAVA_HEAP_RETURN; static void stop_using_full_module_graph(const char* reason = nullptr) NOT_CDS_JAVA_HEAP_RETURN; + + + // Some CDS functions assume that they are called only within a single-threaded context. I.e., + // they are called from: + // - The VM thread (e.g., inside VM_PopulateDumpSharedSpace) + // - The thread that performs prepatory steps before switching to the VM thread + // Since these two threads never execute concurrently, we can avoid using locks in these CDS + // function. For safety, these functions should assert with CDSConfig::current_thread_is_vm_or_dumper(). + class DumperThreadMark { + public: + DumperThreadMark(JavaThread* current); + ~DumperThreadMark(); + }; + + static bool current_thread_is_vm_or_dumper() NOT_CDS_RETURN_(false); }; #endif // SHARE_CDS_CDSCONFIG_HPP diff --git a/src/hotspot/share/cds/cdsEnumKlass.cpp b/src/hotspot/share/cds/cdsEnumKlass.cpp index b77f2fd9d16..17438428c80 100644 --- a/src/hotspot/share/cds/cdsEnumKlass.cpp +++ b/src/hotspot/share/cds/cdsEnumKlass.cpp @@ -39,7 +39,7 @@ bool CDSEnumKlass::is_enum_obj(oop orig_obj) { Klass* k = orig_obj->klass(); Klass* buffered_k = ArchiveBuilder::get_buffered_klass(k); return k->is_instance_klass() && - InstanceKlass::cast(k)->java_super() == vmClasses::Enum_klass(); + InstanceKlass::cast(k)->is_enum_subclass(); } // -- Handling of Enum objects diff --git a/src/hotspot/share/cds/cdsHeapVerifier.cpp b/src/hotspot/share/cds/cdsHeapVerifier.cpp index 29e5af97c6a..fbc58e503ca 100644 --- a/src/hotspot/share/cds/cdsHeapVerifier.cpp +++ b/src/hotspot/share/cds/cdsHeapVerifier.cpp @@ -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 @@ -23,10 +23,14 @@ */ #include "precompiled.hpp" +#include "cds/aotClassInitializer.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsHeapVerifier.hpp" #include "classfile/classLoaderDataGraph.hpp" #include "classfile/javaClasses.inline.hpp" +#include "classfile/moduleEntry.hpp" +#include "classfile/systemDictionaryShared.hpp" +#include "classfile/vmSymbols.hpp" #include "logging/log.hpp" #include "logging/logStream.hpp" #include "memory/resourceArea.hpp" @@ -38,12 +42,13 @@ #if INCLUDE_CDS_JAVA_HEAP // CDSHeapVerifier is used to check for problems where an archived object references a -// static field that may be reinitialized at runtime. In the following example, +// static field that may be get a different value at runtime. In the following example, // Foo.get.test() -// correctly returns true when CDS disabled, but incorrectly returns false when CDS is enabled. +// correctly returns true when CDS disabled, but incorrectly returns false when CDS is enabled, +// because the archived archivedFoo.bar value is different than Bar.bar. // // class Foo { -// final Foo archivedFoo; // this field is archived by CDS +// static final Foo archivedFoo; // this field is archived by CDS // Bar bar; // static { // CDS.initializeFromArchive(Foo.class); @@ -68,8 +73,8 @@ // [2] CDSHeapVerifier::do_entry() checks all the archived objects. None of them // should be in [1] // -// However, it's legal for *some* static fields to be references. This leads to the -// table of ADD_EXCL below. +// However, it's legal for *some* static fields to be referenced. The reasons are explained +// in the table of ADD_EXCL below. // // [A] In most of the cases, the module bootstrap code will update the static field // to point to part of the archived module graph. E.g., @@ -123,6 +128,12 @@ CDSHeapVerifier::CDSHeapVerifier() : _archived_objs(0), _problems(0) ADD_EXCL("sun/invoke/util/ValueConversions", "ONE_INT", // E "ZERO_INT"); // E + if (CDSConfig::is_dumping_invokedynamic()) { + ADD_EXCL("java/lang/invoke/InvokerBytecodeGenerator", "MEMBERNAME_FACTORY", // D + "CD_Object_array", // E same as <...>ConstantUtils.CD_Object_array::CD_Object + "INVOKER_SUPER_DESC"); // E same as java.lang.constant.ConstantDescs::CD_Object + } + # undef ADD_EXCL ClassLoaderDataGraph::classes_do(this); @@ -130,15 +141,16 @@ CDSHeapVerifier::CDSHeapVerifier() : _archived_objs(0), _problems(0) CDSHeapVerifier::~CDSHeapVerifier() { if (_problems > 0) { - log_warning(cds, heap)("Scanned %d objects. Found %d case(s) where " - "an object points to a static field that may be " - "reinitialized at runtime.", _archived_objs, _problems); + log_error(cds, heap)("Scanned %d objects. Found %d case(s) where " + "an object points to a static field that " + "may hold a different value at runtime.", _archived_objs, _problems); + MetaspaceShared::unrecoverable_writing_error(); } } class CDSHeapVerifier::CheckStaticFields : public FieldClosure { CDSHeapVerifier* _verifier; - InstanceKlass* _ik; + InstanceKlass* _ik; // The class whose static fields are being checked. const char** _exclusions; public: CheckStaticFields(CDSHeapVerifier* verifier, InstanceKlass* ik) @@ -151,9 +163,14 @@ public: return; } + if (fd->signature()->equals("Ljdk/internal/access/JavaLangAccess;")) { + // A few classes have static fields that point to SharedSecrets.getJavaLangAccess(). + // This object carries no state and we can create a new one in the production run. + return; + } oop static_obj_field = _ik->java_mirror()->obj_field(fd->offset()); if (static_obj_field != nullptr) { - Klass* klass = static_obj_field->klass(); + Klass* field_type = static_obj_field->klass(); if (_exclusions != nullptr) { for (const char** p = _exclusions; *p != nullptr; p++) { if (fd->name()->equals(*p)) { @@ -173,11 +190,35 @@ public: // This field points to an archived mirror. return; } - if (klass->has_archived_enum_objs()) { - // This klass is a subclass of java.lang.Enum. If any instance of this klass - // has been archived, we will archive all static fields of this klass. - // See HeapShared::initialize_enum_klass(). - return; + + if (field_type->is_instance_klass()) { + InstanceKlass* field_ik = InstanceKlass::cast(field_type); + if (field_ik->is_enum_subclass()) { + if (field_ik->has_archived_enum_objs() || ArchiveUtils::has_aot_initialized_mirror(field_ik)) { + // This field is an Enum. If any instance of this Enum has been archived, we will archive + // all static fields of this Enum as well. + return; + } + } + + if (field_ik->is_hidden() && ArchiveUtils::has_aot_initialized_mirror(field_ik)) { + // We have a static field in a core-library class that points to a method reference, which + // are safe to archive. + guarantee(_ik->module()->name() == vmSymbols::java_base(), "sanity"); + return; + } + + if (field_ik == vmClasses::MethodType_klass()) { + // The identity of MethodTypes are preserved between assembly phase and production runs + // (by MethodType::AOTHolder::archivedMethodTypes). No need to check. + return; + } + + if (field_ik == vmClasses::internal_Unsafe_klass() && ArchiveUtils::has_aot_initialized_mirror(field_ik)) { + // There's only a single instance of jdk/internal/misc/Unsafe, so all references will + // be pointing to this singleton, which has been archived. + return; + } } // This field *may* be initialized to a different value at runtime. Remember it @@ -188,7 +229,8 @@ public: }; // Remember all the static object fields of every class that are currently -// loaded. +// loaded. Later, we will check if any archived objects reference one of +// these fields. void CDSHeapVerifier::do_klass(Klass* k) { if (k->is_instance_klass()) { InstanceKlass* ik = InstanceKlass::cast(k); @@ -200,6 +242,12 @@ void CDSHeapVerifier::do_klass(Klass* k) { return; } + if (ArchiveUtils::has_aot_initialized_mirror(ik)) { + // ik's won't be executed at runtime, the static fields in + // ik will carry their values to runtime. + return; + } + CheckStaticFields csf(this, ik); ik->do_local_static_fields(&csf); } @@ -221,10 +269,15 @@ inline bool CDSHeapVerifier::do_entry(oop& orig_obj, HeapShared::CachedOopInfo& // should be flagged by CDSHeapVerifier. return true; /* keep on iterating */ } + if (info->_holder->is_hidden()) { + return true; + } ResourceMark rm; + char* class_name = info->_holder->name()->as_C_string(); + char* field_name = info->_name->as_C_string(); LogStream ls(Log(cds, heap)::warning()); - ls.print_cr("Archive heap points to a static field that may be reinitialized at runtime:"); - ls.print_cr("Field: %s::%s", info->_holder->name()->as_C_string(), info->_name->as_C_string()); + ls.print_cr("Archive heap points to a static field that may hold a different value at runtime:"); + ls.print_cr("Field: %s::%s", class_name, field_name); ls.print("Value: "); orig_obj->print_on(&ls); ls.print_cr("--- trace begin ---"); @@ -266,6 +319,29 @@ void CDSHeapVerifier::trace_to_root(outputStream* st, oop orig_obj) { } } +const char* static_field_name(oop mirror, oop field) { + Klass* k = java_lang_Class::as_Klass(mirror); + if (k->is_instance_klass()) { + for (JavaFieldStream fs(InstanceKlass::cast(k)); !fs.done(); fs.next()) { + if (fs.access_flags().is_static()) { + fieldDescriptor& fd = fs.field_descriptor(); + switch (fd.field_type()) { + case T_OBJECT: + case T_ARRAY: + if (mirror->obj_field(fd.offset()) == field) { + return fs.name()->as_C_string(); + } + break; + default: + break; + } + } + } + } + + return ""; +} + int CDSHeapVerifier::trace_to_root(outputStream* st, oop orig_obj, oop orig_field, HeapShared::CachedOopInfo* info) { int level = 0; if (info->orig_referrer() != nullptr) { @@ -280,6 +356,9 @@ int CDSHeapVerifier::trace_to_root(outputStream* st, oop orig_obj, oop orig_fiel st->print("[%2d] ", level); orig_obj->print_address_on(st); st->print(" %s", k->internal_name()); + if (java_lang_Class::is_instance(orig_obj)) { + st->print(" (%s::%s)", java_lang_Class::as_Klass(orig_obj)->external_name(), static_field_name(orig_obj, orig_field)); + } if (orig_field != nullptr) { if (k->is_instance_klass()) { TraceFields clo(orig_obj, orig_field, st); diff --git a/src/hotspot/share/cds/cds_globals.hpp b/src/hotspot/share/cds/cds_globals.hpp index 828393a7b6b..38f5d8f46a6 100644 --- a/src/hotspot/share/cds/cds_globals.hpp +++ b/src/hotspot/share/cds/cds_globals.hpp @@ -94,6 +94,30 @@ "(2) always map at preferred address, and if unsuccessful, " \ "do not map the archive") \ range(0, 2) \ + \ + /*========== New "AOT" flags =========================================*/ \ + /* The following 3 flags are aliases of -Xshare:dump, */ \ + /* -XX:SharedArchiveFile=..., etc. See CDSConfig::check_flag_aliases()*/ \ + \ + product(ccstr, AOTMode, nullptr, \ + "Specifies how AOTCache should be created or used. Valid values " \ + "are: off, record, create, auto, on; the default is auto") \ + constraint(AOTModeConstraintFunc, AtParse) \ + \ + product(ccstr, AOTConfiguration, nullptr, \ + "Configuration information used by CreateAOTCache") \ + \ + product(ccstr, AOTCache, nullptr, \ + "Cache for improving start up and warm up") \ + \ + product(bool, AOTInvokeDynamicLinking, false, DIAGNOSTIC, \ + "AOT-link JVM_CONSTANT_InvokeDynamic entries in cached " \ + "ConstantPools") \ + \ + product(bool, AOTClassLinking, false, \ + "Load/link all archived classes for the boot/platform/app " \ + "loaders before application main") \ + // end of CDS_FLAGS DECLARE_FLAGS(CDS_FLAGS) diff --git a/src/hotspot/share/cds/classListParser.cpp b/src/hotspot/share/cds/classListParser.cpp index 694a179d7ee..57d14229795 100644 --- a/src/hotspot/share/cds/classListParser.cpp +++ b/src/hotspot/share/cds/classListParser.cpp @@ -23,9 +23,9 @@ */ #include "precompiled.hpp" +#include "cds/aotConstantPoolResolver.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" @@ -46,6 +46,7 @@ #include "memory/resourceArea.hpp" #include "oops/constantPool.inline.hpp" #include "runtime/atomic.hpp" +#include "runtime/globals_extension.hpp" #include "runtime/handles.inline.hpp" #include "runtime/java.hpp" #include "runtime/javaCalls.hpp" @@ -69,9 +70,13 @@ ClassListParser::ClassListParser(const char* file, ParseMode parse_mode) : log_info(cds)("Parsing %s%s", file, parse_lambda_forms_invokers_only() ? " (lambda form invokers only)" : ""); if (!_file_input.is_open()) { - char errmsg[JVM_MAXPATHLEN]; - os::lasterror(errmsg, JVM_MAXPATHLEN); - vm_exit_during_initialization("Loading classlist failed", errmsg); + char reason[JVM_MAXPATHLEN]; + os::lasterror(reason, JVM_MAXPATHLEN); + vm_exit_during_initialization(err_msg("Loading %s %s failed", + FLAG_IS_DEFAULT(AOTConfiguration) ? + "classlist" : "AOTConfiguration file", + file), + reason); } _token = _line = nullptr; _interfaces = new (mtClass) GrowableArray(10, mtClass); @@ -594,6 +599,18 @@ void ClassListParser::resolve_indy(JavaThread* current, Symbol* class_name_symbo } void ClassListParser::resolve_indy_impl(Symbol* class_name_symbol, TRAPS) { + if (CDSConfig::is_dumping_invokedynamic()) { + // The CP entry for the invokedynamic instruction will be resolved. + // No need to do the following. + return; + } + + // This is an older CDS optimization: + // We store a pre-generated version of the lambda proxy class in the AOT cache, + // which will be loaded via JVM_LookupLambdaProxyClassFromArchive(). + // This eliminate dynamic class generation of the proxy class, but we still need to + // resolve the CP entry for the invokedynamic instruction, which may result in + // generation of LambdaForm classes. Handle class_loader(THREAD, SystemDictionary::java_system_loader()); Handle protection_domain; Klass* klass = SystemDictionary::resolve_or_fail(class_name_symbol, class_loader, protection_domain, true, CHECK); @@ -836,6 +853,8 @@ void ClassListParser::parse_constant_pool_tag() { case JVM_CONSTANT_InterfaceMethodref: preresolve_fmi = true; break; + case JVM_CONSTANT_InvokeDynamic: + preresolve_indy = true; break; default: constant_pool_resolution_warning("Unsupported constant pool index %d: %s (type=%d)", @@ -845,9 +864,12 @@ void ClassListParser::parse_constant_pool_tag() { } if (preresolve_class) { - ClassPrelinker::preresolve_class_cp_entries(THREAD, ik, &preresolve_list); + AOTConstantPoolResolver::preresolve_class_cp_entries(THREAD, ik, &preresolve_list); } if (preresolve_fmi) { - ClassPrelinker::preresolve_field_and_method_cp_entries(THREAD, ik, &preresolve_list); + AOTConstantPoolResolver::preresolve_field_and_method_cp_entries(THREAD, ik, &preresolve_list); + } + if (preresolve_indy) { + AOTConstantPoolResolver::preresolve_indy_cp_entries(THREAD, ik, &preresolve_list); } } diff --git a/src/hotspot/share/cds/classListWriter.cpp b/src/hotspot/share/cds/classListWriter.cpp index 1b9f589f1c5..78a6857ff73 100644 --- a/src/hotspot/share/cds/classListWriter.cpp +++ b/src/hotspot/share/cds/classListWriter.cpp @@ -128,9 +128,15 @@ void ClassListWriter::write_to_stream(const InstanceKlass* k, outputStream* stre } } - // filter out java/lang/invoke/BoundMethodHandle$Species... - if (cfs != nullptr && cfs->source() != nullptr && strcmp(cfs->source(), "_ClassSpecializer_generateConcreteSpeciesCode") == 0) { - return; + if (cfs != nullptr && cfs->source() != nullptr) { + if (strcmp(cfs->source(), "_ClassSpecializer_generateConcreteSpeciesCode") == 0) { + return; + } + + if (strncmp(cfs->source(), "__", 2) == 0) { + // generated class: __dynamic_proxy__, __JVM_LookupDefineClass__, etc + return; + } } { @@ -256,6 +262,18 @@ void ClassListWriter::write_resolved_constants_for(InstanceKlass* ik) { } if (cp->cache() != nullptr) { + Array* indy_entries = cp->cache()->resolved_indy_entries(); + if (indy_entries != nullptr) { + for (int i = 0; i < indy_entries->length(); i++) { + ResolvedIndyEntry* rie = indy_entries->adr_at(i); + int cp_index = rie->constant_pool_index(); + if (rie->is_resolved()) { + list.at_put(cp_index, true); + print = true; + } + } + } + Array* field_entries = cp->cache()->resolved_field_entries(); if (field_entries != nullptr) { for (int i = 0; i < field_entries->length(); i++) { @@ -274,7 +292,8 @@ void ClassListWriter::write_resolved_constants_for(InstanceKlass* ik) { ResolvedMethodEntry* rme = method_entries->adr_at(i); if (rme->is_resolved(Bytecodes::_invokevirtual) || rme->is_resolved(Bytecodes::_invokespecial) || - rme->is_resolved(Bytecodes::_invokeinterface)) { + rme->is_resolved(Bytecodes::_invokeinterface) || + rme->is_resolved(Bytecodes::_invokehandle)) { list.at_put(rme->constant_pool_index(), true); print = true; } @@ -291,7 +310,8 @@ void ClassListWriter::write_resolved_constants_for(InstanceKlass* ik) { assert(cp_tag.value() == JVM_CONSTANT_Class || cp_tag.value() == JVM_CONSTANT_Fieldref || cp_tag.value() == JVM_CONSTANT_Methodref|| - cp_tag.value() == JVM_CONSTANT_InterfaceMethodref, "sanity"); + cp_tag.value() == JVM_CONSTANT_InterfaceMethodref || + cp_tag.value() == JVM_CONSTANT_InvokeDynamic, "sanity"); stream->print(" %d", i); } } diff --git a/src/hotspot/share/cds/classPrelinker.cpp b/src/hotspot/share/cds/classPrelinker.cpp deleted file mode 100644 index 6b866bac995..00000000000 --- a/src/hotspot/share/cds/classPrelinker.cpp +++ /dev/null @@ -1,345 +0,0 @@ -/* - * 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 - * 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. - * - */ - -#include "precompiled.hpp" -#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" -#include "oops/klass.inline.hpp" -#include "runtime/handles.inline.hpp" - -ClassPrelinker::ClassesTable* ClassPrelinker::_processed_classes = nullptr; -ClassPrelinker::ClassesTable* ClassPrelinker::_vm_classes = nullptr; - -bool ClassPrelinker::is_vm_class(InstanceKlass* ik) { - return (_vm_classes->get(ik) != nullptr); -} - -void ClassPrelinker::add_one_vm_class(InstanceKlass* ik) { - bool created; - _vm_classes->put_if_absent(ik, &created); - if (created) { - InstanceKlass* super = ik->java_super(); - if (super != nullptr) { - add_one_vm_class(super); - } - Array* ifs = ik->local_interfaces(); - for (int i = 0; i < ifs->length(); i++) { - add_one_vm_class(ifs->at(i)); - } - } -} - -void ClassPrelinker::initialize() { - assert(_vm_classes == nullptr, "must be"); - _vm_classes = new (mtClass)ClassesTable(); - _processed_classes = new (mtClass)ClassesTable(); - for (auto id : EnumRange{}) { - add_one_vm_class(vmClasses::klass_at(id)); - } -} - -void ClassPrelinker::dispose() { - assert(_vm_classes != nullptr, "must be"); - delete _vm_classes; - delete _processed_classes; - _vm_classes = nullptr; - _processed_classes = nullptr; -} - -// 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"); - - 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() || - cp->tag_at(cp_index).is_method() || - cp->tag_at(cp_index).is_interface_method()) { - 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; - } - - 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::is_class_resolution_deterministic(InstanceKlass* cp_holder, Klass* resolved_class) { - assert(!is_in_archivebuilder_buffer(cp_holder), "sanity"); - assert(!is_in_archivebuilder_buffer(resolved_class), "sanity"); - - if (resolved_class->is_instance_klass()) { - InstanceKlass* ik = InstanceKlass::cast(resolved_class); - - if (!ik->is_shared() && SystemDictionaryShared::is_excluded_class(ik)) { - return false; - } - - if (cp_holder->is_subtype_of(ik)) { - // All super types of ik will be resolved in ik->class_loader() before - // ik is defined in this loader, so it's safe to archive the resolved klass reference. - return true; - } - - 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) { - if (!ik->is_linked()) { - return; - } - bool first_time; - _processed_classes->put_if_absent(ik, &first_time); - if (!first_time) { - // We have already resolved the constants in class, so no need to do it again. - 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_String: - resolve_string(cp, cp_index, CHECK); // may throw OOM when interning strings. - break; - } - } -} - -// 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 (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
(h_loader()), - cast_from_oop
(SystemDictionary::java_system_loader()), - cast_from_oop
(SystemDictionary::java_platform_loader())); - } - - return nullptr; -} - -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 -void ClassPrelinker::resolve_string(constantPoolHandle cp, int cp_index, TRAPS) { - if (CDSConfig::is_dumping_heap()) { - int cache_index = cp->cp_to_object_index(cp_index); - ConstantPool::string_at_impl(cp, cp_index, cache_index, CHECK); - } -} -#endif - -void ClassPrelinker::preresolve_class_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* 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* 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; - case Bytecodes::_invokespecial: - case Bytecodes::_invokevirtual: - case Bytecodes::_invokeinterface: - 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* 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; - - case Bytecodes::_invokevirtual: - case Bytecodes::_invokespecial: - case Bytecodes::_invokeinterface: - InterpreterRuntime::cds_resolve_invoke(bc, raw_index, cp, 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) { - return false; - } else { - return ArchiveBuilder::current()->is_in_buffer_space(p); - } -} -#endif diff --git a/src/hotspot/share/cds/dumpAllocStats.cpp b/src/hotspot/share/cds/dumpAllocStats.cpp index 25351f78d84..e88a77de7ad 100644 --- a/src/hotspot/share/cds/dumpAllocStats.cpp +++ b/src/hotspot/share/cds/dumpAllocStats.cpp @@ -23,6 +23,7 @@ */ #include "precompiled.hpp" +#include "cds/aotClassLinker.hpp" #include "cds/dumpAllocStats.hpp" #include "logging/log.hpp" #include "logging/logMessage.hpp" @@ -114,6 +115,12 @@ void DumpAllocStats::print_stats(int ro_all, int rw_all) { _num_method_cp_entries, _num_method_cp_entries_archived, percent_of(_num_method_cp_entries_archived, _num_method_cp_entries), _num_method_cp_entries_reverted); + msg.info("Indy CP entries = %6d, archived = %6d (%5.1f%%), reverted = %6d", + _num_indy_cp_entries, _num_indy_cp_entries_archived, + percent_of(_num_indy_cp_entries_archived, _num_indy_cp_entries), + _num_indy_cp_entries_reverted); + msg.info("Platform loader initiated classes = %5d", AOTClassLinker::num_platform_initiated_classes()); + msg.info("App loader initiated classes = %5d", AOTClassLinker::num_app_initiated_classes()); } #ifdef ASSERT diff --git a/src/hotspot/share/cds/dumpAllocStats.hpp b/src/hotspot/share/cds/dumpAllocStats.hpp index 2ff81c52392..7d651320e6f 100644 --- a/src/hotspot/share/cds/dumpAllocStats.hpp +++ b/src/hotspot/share/cds/dumpAllocStats.hpp @@ -68,6 +68,9 @@ public: int _num_field_cp_entries; int _num_field_cp_entries_archived; int _num_field_cp_entries_reverted; + int _num_indy_cp_entries; + int _num_indy_cp_entries_archived; + int _num_indy_cp_entries_reverted; int _num_klass_cp_entries; int _num_klass_cp_entries_archived; int _num_klass_cp_entries_reverted; @@ -84,6 +87,9 @@ public: _num_field_cp_entries = 0; _num_field_cp_entries_archived = 0; _num_field_cp_entries_reverted = 0; + _num_indy_cp_entries = 0; + _num_indy_cp_entries_archived = 0; + _num_indy_cp_entries_reverted = 0; _num_klass_cp_entries = 0; _num_klass_cp_entries_archived = 0; _num_klass_cp_entries_reverted = 0; @@ -122,6 +128,12 @@ public: _num_field_cp_entries_reverted += reverted ? 1 : 0; } + void record_indy_cp_entry(bool archived, bool reverted) { + _num_indy_cp_entries ++; + _num_indy_cp_entries_archived += archived ? 1 : 0; + _num_indy_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; diff --git a/src/hotspot/share/cds/dumpTimeClassInfo.hpp b/src/hotspot/share/cds/dumpTimeClassInfo.hpp index 5ba79e54c79..c060601f1fb 100644 --- a/src/hotspot/share/cds/dumpTimeClassInfo.hpp +++ b/src/hotspot/share/cds/dumpTimeClassInfo.hpp @@ -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 @@ -42,7 +42,8 @@ class DumpTimeClassInfo: public CHeapObj { bool _excluded; bool _is_early_klass; bool _has_checked_exclusion; - + bool _is_required_hidden_class; + bool _has_scanned_constant_pool; class DTLoaderConstraint { Symbol* _name; char _loader_type1; @@ -137,6 +138,8 @@ public: _failed_verification = false; _is_archived_lambda_proxy = false; _has_checked_exclusion = false; + _is_required_hidden_class = false; + _has_scanned_constant_pool = false; _id = -1; _clsfile_size = -1; _clsfile_crc32 = -1; @@ -214,6 +217,11 @@ public: InstanceKlass* nest_host() const { return _nest_host; } void set_nest_host(InstanceKlass* nest_host) { _nest_host = nest_host; } + bool is_required_hidden_class() const { return _is_required_hidden_class; } + void set_is_required_hidden_class() { _is_required_hidden_class = true; } + bool has_scanned_constant_pool() const { return _has_scanned_constant_pool; } + void set_has_scanned_constant_pool() { _has_scanned_constant_pool = true; } + size_t runtime_info_bytesize() const; }; diff --git a/src/hotspot/share/cds/dynamicArchive.cpp b/src/hotspot/share/cds/dynamicArchive.cpp index 04c60b89580..f102282f682 100644 --- a/src/hotspot/share/cds/dynamicArchive.cpp +++ b/src/hotspot/share/cds/dynamicArchive.cpp @@ -23,12 +23,12 @@ */ #include "precompiled.hpp" +#include "cds/aotClassLinker.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveHeapWriter.hpp" #include "cds/archiveUtils.inline.hpp" #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" -#include "cds/classPrelinker.hpp" #include "cds/dynamicArchive.hpp" #include "cds/regeneratedClasses.hpp" #include "classfile/classLoader.hpp" @@ -47,8 +47,8 @@ #include "runtime/arguments.hpp" #include "runtime/os.hpp" #include "runtime/sharedRuntime.hpp" -#include "runtime/vmThread.hpp" #include "runtime/vmOperations.hpp" +#include "runtime/vmThread.hpp" #include "utilities/align.hpp" #include "utilities/bitMap.inline.hpp" @@ -112,7 +112,7 @@ public: // Block concurrent class unloading from changing the _dumptime_table MutexLocker ml(DumpTimeTable_lock, Mutex::_no_safepoint_check_flag); - SystemDictionaryShared::check_excluded_classes(); + SystemDictionaryShared::find_all_archivable_classes(); if (SystemDictionaryShared::is_dumptime_table_empty()) { log_warning(cds, dynamic)("There is no class to be included in the dynamic archive."); @@ -135,6 +135,11 @@ public: verify_estimate_size(_estimated_metaspaceobj_bytes, "MetaspaceObjs"); + sort_methods(); + + log_info(cds)("Make classes shareable"); + make_klasses_shareable(); + char* serialized_data; { // Write the symbol table and system dictionaries to the RO space. @@ -147,6 +152,7 @@ public: ArchiveBuilder::OtherROAllocMark mark; SystemDictionaryShared::write_to_archive(false); DynamicArchive::dump_array_klasses(); + AOTClassLinker::write_to_archive(); serialized_data = ro_region()->top(); WriteClosure wc(ro_region()); @@ -155,11 +161,6 @@ public: verify_estimate_size(_estimated_hashtable_bytes, "Hashtables"); - sort_methods(); - - log_info(cds)("Make classes shareable"); - make_klasses_shareable(); - log_info(cds)("Adjust lambda proxy class dictionary"); SystemDictionaryShared::adjust_lambda_proxy_class_dictionary(); @@ -234,7 +235,7 @@ void DynamicArchiveBuilder::release_header() { void DynamicArchiveBuilder::post_dump() { ArchivePtrMarker::reset_map_and_vs(); - ClassPrelinker::dispose(); + AOTClassLinker::dispose(); } void DynamicArchiveBuilder::sort_methods() { @@ -497,6 +498,7 @@ void DynamicArchive::check_for_dynamic_dump() { void DynamicArchive::dump_at_exit(JavaThread* current, const char* archive_name) { ExceptionMark em(current); ResourceMark rm(current); + CDSConfig::DumperThreadMark dumper_thread_mark(current); if (!CDSConfig::is_dumping_dynamic_archive() || archive_name == nullptr) { return; @@ -526,6 +528,7 @@ void DynamicArchive::dump_at_exit(JavaThread* current, const char* archive_name) // This is called by "jcmd VM.cds dynamic_dump" void DynamicArchive::dump_for_jcmd(const char* archive_name, TRAPS) { + CDSConfig::DumperThreadMark dumper_thread_mark(THREAD); assert(CDSConfig::is_using_archive() && RecordDynamicDumpInfo, "already checked in arguments.cpp"); assert(ArchiveClassesAtExit == nullptr, "already checked in arguments.cpp"); assert(CDSConfig::is_dumping_dynamic_archive(), "already checked by check_for_dynamic_dump() during VM startup"); diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp index 5e88b3c47e4..00d8fba4411 100644 --- a/src/hotspot/share/cds/filemap.cpp +++ b/src/hotspot/share/cds/filemap.cpp @@ -223,7 +223,9 @@ void FileMapHeader::populate(FileMapInfo *info, size_t core_region_alignment, } _max_heap_size = MaxHeapSize; _use_optimized_module_handling = CDSConfig::is_using_optimized_module_handling(); + _has_aot_linked_classes = CDSConfig::is_dumping_aot_linked_classes(); _has_full_module_graph = CDSConfig::is_dumping_full_module_graph(); + _has_archived_invokedynamic = CDSConfig::is_dumping_invokedynamic(); // The following fields are for sanity checks for whether this archive // will function correctly with this JVM and the bootclasspath it's @@ -313,6 +315,8 @@ void FileMapHeader::print(outputStream* st) { st->print_cr("- allow_archiving_with_java_agent:%d", _allow_archiving_with_java_agent); st->print_cr("- use_optimized_module_handling: %d", _use_optimized_module_handling); st->print_cr("- has_full_module_graph %d", _has_full_module_graph); + st->print_cr("- has_aot_linked_classes %d", _has_aot_linked_classes); + st->print_cr("- has_archived_invokedynamic %d", _has_archived_invokedynamic); } void SharedClassPathEntry::init_as_non_existent(const char* path, TRAPS) { @@ -1060,7 +1064,9 @@ bool FileMapInfo::validate_shared_path_table() { } } - validate_non_existent_class_paths(); + if (!validate_non_existent_class_paths()) { + return false; + } _validating_shared_path_table = false; @@ -1076,7 +1082,7 @@ bool FileMapInfo::validate_shared_path_table() { return true; } -void FileMapInfo::validate_non_existent_class_paths() { +bool FileMapInfo::validate_non_existent_class_paths() { // All of the recorded non-existent paths came from the Class-Path: attribute from the JAR // files on the app classpath. If any of these are found to exist during runtime, // it will change how classes are loading for the app loader. For safety, disable @@ -1089,11 +1095,19 @@ void FileMapInfo::validate_non_existent_class_paths() { i++) { SharedClassPathEntry* ent = shared_path(i); if (!ent->check_non_existent()) { - log_warning(cds)("Archived non-system classes are disabled because the " - "file %s exists", ent->name()); - header()->set_has_platform_or_app_classes(false); + if (header()->has_aot_linked_classes()) { + log_error(cds)("CDS archive has aot-linked classes. It cannot be used because the " + "file %s exists", ent->name()); + return false; + } else { + log_warning(cds)("Archived non-system classes are disabled because the " + "file %s exists", ent->name()); + header()->set_has_platform_or_app_classes(false); + } } } + + return true; } // A utility class for reading/validating the GenericCDSFileMapHeader portion of @@ -2074,7 +2088,16 @@ void FileMapInfo::map_or_load_heap_region() { } if (!success) { - CDSConfig::stop_using_full_module_graph(); + if (CDSConfig::is_using_aot_linked_classes()) { + // It's too late to recover -- we have already committed to use the archived metaspace objects, but + // the archived heap objects cannot be loaded, so we don't have the archived FMG to guarantee that + // all AOT-linked classes are visible. + // + // We get here because the heap is too small. The app will fail anyway. So let's quit. + MetaspaceShared::unrecoverable_loading_error("CDS archive has aot-linked classes but the archived " + "heap objects cannot be loaded. Try increasing your heap size."); + } + CDSConfig::stop_using_full_module_graph("archive heap loading failed"); } } @@ -2429,6 +2452,34 @@ bool FileMapInfo::initialize() { return true; } +bool FileMapInfo::validate_aot_class_linking() { + // These checks need to be done after FileMapInfo::initialize(), which gets called before Universe::heap() + // is available. + if (header()->has_aot_linked_classes()) { + CDSConfig::set_has_aot_linked_classes(true); + if (JvmtiExport::should_post_class_file_load_hook()) { + log_error(cds)("CDS archive has aot-linked classes. It cannot be used when JVMTI ClassFileLoadHook is in use."); + return false; + } + if (JvmtiExport::has_early_vmstart_env()) { + log_error(cds)("CDS archive has aot-linked classes. It cannot be used when JVMTI early vm start is in use."); + return false; + } + if (!CDSConfig::is_using_full_module_graph()) { + log_error(cds)("CDS archive has aot-linked classes. It cannot be used when archived full module graph is not used."); + return false; + } + + const char* prop = Arguments::get_property("java.security.manager"); + if (prop != nullptr && strcmp(prop, "disallow") != 0) { + log_error(cds)("CDS archive has aot-linked classes. It cannot be used with -Djava.security.manager=%s.", prop); + return false; + } + } + + return true; +} + // The 2 core spaces are RW->RO FileMapRegion* FileMapInfo::first_core_region() const { return region_at(MetaspaceShared::rw); @@ -2478,6 +2529,11 @@ bool FileMapHeader::validate() { // header data const char* prop = Arguments::get_property("java.system.class.loader"); if (prop != nullptr) { + if (has_aot_linked_classes()) { + log_error(cds)("CDS archive has aot-linked classes. It cannot be used when the " + "java.system.class.loader property is specified."); + return false; + } log_warning(cds)("Archived non-system classes are disabled because the " "java.system.class.loader property is specified (value = \"%s\"). " "To use archived non-system classes, this property must not be set", prop); @@ -2542,9 +2598,15 @@ bool FileMapHeader::validate() { log_info(cds)("optimized module handling: disabled because archive was created without optimized module handling"); } - if (is_static() && !_has_full_module_graph) { + if (is_static()) { // Only the static archive can contain the full module graph. - CDSConfig::stop_using_full_module_graph("archive was created without full module graph"); + if (!_has_full_module_graph) { + CDSConfig::stop_using_full_module_graph("archive was created without full module graph"); + } + + if (_has_archived_invokedynamic) { + CDSConfig::set_has_archived_invokedynamic(); + } } return true; diff --git a/src/hotspot/share/cds/filemap.hpp b/src/hotspot/share/cds/filemap.hpp index 6a15ff03c3a..6319c51f1ce 100644 --- a/src/hotspot/share/cds/filemap.hpp +++ b/src/hotspot/share/cds/filemap.hpp @@ -228,7 +228,9 @@ private: bool _allow_archiving_with_java_agent; // setting of the AllowArchivingWithJavaAgent option bool _use_optimized_module_handling;// No module-relation VM options were specified, so we can skip // some expensive operations. + bool _has_aot_linked_classes; // Was the CDS archive created with -XX:+AOTClassLinking bool _has_full_module_graph; // Does this CDS archive contain the full archived module graph? + bool _has_archived_invokedynamic; // Does the archive have aot-linked invokedynamic CP entries? HeapRootSegments _heap_root_segments; // Heap root segments info size_t _heap_oopmap_start_pos; // The first bit in the oopmap corresponds to this position in the heap. size_t _heap_ptrmap_start_pos; // The first bit in the ptrmap corresponds to this position in the heap. @@ -273,6 +275,7 @@ public: char* mapped_base_address() const { return _mapped_base_address; } bool has_platform_or_app_classes() const { return _has_platform_or_app_classes; } bool has_non_jar_in_classpath() const { return _has_non_jar_in_classpath; } + bool has_aot_linked_classes() const { return _has_aot_linked_classes; } bool compressed_oops() const { return _compressed_oops; } bool compressed_class_pointers() const { return _compressed_class_ptrs; } int narrow_klass_pointer_bits() const { return _narrow_klass_pointer_bits; } @@ -492,7 +495,8 @@ public: static void check_nonempty_dir_in_shared_path_table(); bool check_module_paths(); bool validate_shared_path_table(); - void validate_non_existent_class_paths(); + bool validate_non_existent_class_paths(); + bool validate_aot_class_linking(); static void set_shared_path_table(FileMapInfo* info) { _shared_path_table = info->header()->shared_path_table(); } diff --git a/src/hotspot/share/cds/heapShared.cpp b/src/hotspot/share/cds/heapShared.cpp index 22040448770..3a7ba936bb0 100644 --- a/src/hotspot/share/cds/heapShared.cpp +++ b/src/hotspot/share/cds/heapShared.cpp @@ -23,6 +23,7 @@ */ #include "precompiled.hpp" +#include "cds/aotClassInitializer.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveHeapLoader.hpp" #include "cds/archiveHeapWriter.hpp" @@ -97,7 +98,7 @@ size_t HeapShared::_total_obj_size; #define ARCHIVE_TEST_FIELD_NAME "archivedObjects" static Array* _archived_ArchiveHeapTestClass = nullptr; static const char* _test_class_name = nullptr; -static const Klass* _test_class = nullptr; +static Klass* _test_class = nullptr; static const ArchivedKlassSubGraphInfoRecord* _test_class_record = nullptr; #endif @@ -119,6 +120,7 @@ static ArchivableStaticFieldInfo archive_subgraph_entry_fields[] = { {"java/lang/ModuleLayer", "EMPTY_LAYER"}, {"java/lang/module/Configuration", "EMPTY_CONFIGURATION"}, {"jdk/internal/math/FDBigInteger", "archivedCaches"}, + #ifndef PRODUCT {nullptr, nullptr}, // Extra slot for -XX:ArchiveHeapTestClass #endif @@ -133,7 +135,8 @@ static ArchivableStaticFieldInfo fmg_archive_subgraph_entry_fields[] = { {nullptr, nullptr}, }; -KlassSubGraphInfo* HeapShared::_default_subgraph_info; +KlassSubGraphInfo* HeapShared::_dump_time_special_subgraph; +ArchivedKlassSubGraphInfoRecord* HeapShared::_run_time_special_subgraph; GrowableArrayCHeap* HeapShared::_pending_roots = nullptr; GrowableArrayCHeap* HeapShared::_root_segments; int HeapShared::_root_segment_max_size_elems; @@ -298,6 +301,7 @@ bool HeapShared::archive_object(oop obj) { if (ArchiveHeapWriter::is_too_large_to_archive(obj->size())) { log_debug(cds, heap)("Cannot archive, object (" PTR_FORMAT ") is too large: " SIZE_FORMAT, p2i(obj), obj->size()); + debug_trace(); return false; } else { count_allocation(obj->size()); @@ -309,8 +313,19 @@ bool HeapShared::archive_object(oop obj) { if (log_is_enabled(Debug, cds, heap)) { ResourceMark rm; - log_debug(cds, heap)("Archived heap object " PTR_FORMAT " : %s", - p2i(obj), obj->klass()->external_name()); + LogTarget(Debug, cds, heap) log; + LogStream out(log); + out.print("Archived heap object " PTR_FORMAT " : %s ", + p2i(obj), obj->klass()->external_name()); + if (java_lang_Class::is_instance(obj)) { + Klass* k = java_lang_Class::as_Klass(obj); + if (k != nullptr) { + out.print("%s", k->external_name()); + } else { + out.print("primitive"); + } + } + out.cr(); } if (java_lang_Module::is_instance(obj) && Modules::check_archived_module_oop(obj)) { @@ -371,6 +386,31 @@ void HeapShared::init_scratch_objects(TRAPS) { _scratch_references_table = new (mtClass)MetaspaceObjToOopHandleTable(); } +// Given java_mirror that represents a (primitive or reference) type T, +// return the "scratch" version that represents the same type T. +// Note that if java_mirror will be returned if it's already a +// scratch mirror. +// +// See java_lang_Class::create_scratch_mirror() for more info. +oop HeapShared::scratch_java_mirror(oop java_mirror) { + assert(java_lang_Class::is_instance(java_mirror), "must be"); + + for (int i = T_BOOLEAN; i < T_VOID+1; i++) { + BasicType bt = (BasicType)i; + if (!is_reference_type(bt)) { + if (_scratch_basic_type_mirrors[i].resolve() == java_mirror) { + return java_mirror; + } + } + } + + if (java_lang_Class::is_primitive(java_mirror)) { + return scratch_java_mirror(java_lang_Class::as_BasicType(java_mirror)); + } else { + return scratch_java_mirror(java_lang_Class::as_Klass(java_mirror)); + } +} + oop HeapShared::scratch_java_mirror(BasicType t) { assert((uint)t < T_VOID+1, "range check"); assert(!is_reference_type(t), "sanity"); @@ -399,13 +439,134 @@ void HeapShared::remove_scratch_objects(Klass* k) { } } +//TODO: we eventually want a more direct test for these kinds of things. +//For example the JVM could record some bit of context from the creation +//of the klass, such as who called the hidden class factory. Using +//string compares on names is fragile and will break as soon as somebody +//changes the names in the JDK code. See discussion in JDK-8342481 for +//related ideas about marking AOT-related classes. +bool HeapShared::is_lambda_form_klass(InstanceKlass* ik) { + return ik->is_hidden() && + (ik->name()->starts_with("java/lang/invoke/LambdaForm$MH+") || + ik->name()->starts_with("java/lang/invoke/LambdaForm$DMH+") || + ik->name()->starts_with("java/lang/invoke/LambdaForm$BMH+") || + ik->name()->starts_with("java/lang/invoke/LambdaForm$VH+")); +} + +bool HeapShared::is_lambda_proxy_klass(InstanceKlass* ik) { + return ik->is_hidden() && (ik->name()->index_of_at(0, "$$Lambda+", 9) > 0); +} + +bool HeapShared::is_string_concat_klass(InstanceKlass* ik) { + return ik->is_hidden() && ik->name()->starts_with("java/lang/String$$StringConcat"); +} + +bool HeapShared::is_archivable_hidden_klass(InstanceKlass* ik) { + return CDSConfig::is_dumping_invokedynamic() && + (is_lambda_form_klass(ik) || is_lambda_proxy_klass(ik) || is_string_concat_klass(ik)); +} + +void HeapShared::copy_aot_initialized_mirror(Klass* orig_k, oop orig_mirror, oop m) { + assert(orig_k->is_instance_klass(), "sanity"); + InstanceKlass* ik = InstanceKlass::cast(orig_k); + InstanceKlass* buffered_ik = ArchiveBuilder::current()->get_buffered_addr(ik); + + assert(ik->is_initialized(), "must be"); + + int nfields = 0; + for (JavaFieldStream fs(ik); !fs.done(); fs.next()) { + if (fs.access_flags().is_static()) { + fieldDescriptor& fd = fs.field_descriptor(); + int offset = fd.offset(); + switch (fd.field_type()) { + case T_OBJECT: + case T_ARRAY: + m->obj_field_put(offset, orig_mirror->obj_field(offset)); + break; + case T_BOOLEAN: + m->bool_field_put(offset, orig_mirror->bool_field(offset)); + break; + case T_BYTE: + m->byte_field_put(offset, orig_mirror->byte_field(offset)); + break; + case T_SHORT: + m->short_field_put(offset, orig_mirror->short_field(offset)); + break; + case T_CHAR: + m->char_field_put(offset, orig_mirror->char_field(offset)); + break; + case T_INT: + m->int_field_put(offset, orig_mirror->int_field(offset)); + break; + case T_LONG: + m->long_field_put(offset, orig_mirror->long_field(offset)); + break; + case T_FLOAT: + m->float_field_put(offset, orig_mirror->float_field(offset)); + break; + case T_DOUBLE: + m->double_field_put(offset, orig_mirror->double_field(offset)); + break; + default: + ShouldNotReachHere(); + } + nfields ++; + } + } + + java_lang_Class::set_class_data(m, java_lang_Class::class_data(orig_mirror)); + + // Class::reflectData use SoftReference, which cannot be archived. Set it + // to null and it will be recreated at runtime. + java_lang_Class::set_reflection_data(m, nullptr); + + if (log_is_enabled(Info, cds, init)) { + ResourceMark rm; + log_debug(cds, init)("copied %3d field(s) in aot-initialized mirror %s%s", nfields, ik->external_name(), + ik->is_hidden() ? " (hidden)" : ""); + } +} + +static void copy_java_mirror_hashcode(oop orig_mirror, oop scratch_m) { + // We need to retain the identity_hash, because it may have been used by some hashtables + // in the shared heap. + if (!orig_mirror->fast_no_hash_check()) { + intptr_t src_hash = orig_mirror->identity_hash(); + if (UseCompactObjectHeaders) { + narrowKlass nk = CompressedKlassPointers::encode(orig_mirror->klass()); + scratch_m->set_mark(markWord::prototype().set_narrow_klass(nk).copy_set_hash(src_hash)); + } else { + scratch_m->set_mark(markWord::prototype().copy_set_hash(src_hash)); + } + assert(scratch_m->mark().is_unlocked(), "sanity"); + + DEBUG_ONLY(intptr_t archived_hash = scratch_m->identity_hash()); + assert(src_hash == archived_hash, "Different hash codes: original " INTPTR_FORMAT ", archived " INTPTR_FORMAT, src_hash, archived_hash); + } +} + +static objArrayOop get_archived_resolved_references(InstanceKlass* src_ik) { + InstanceKlass* buffered_ik = ArchiveBuilder::current()->get_buffered_addr(src_ik); + if (buffered_ik->is_shared_boot_class() || + buffered_ik->is_shared_platform_class() || + buffered_ik->is_shared_app_class()) { + objArrayOop rr = src_ik->constants()->resolved_references_or_null(); + if (rr != nullptr && !ArchiveHeapWriter::is_too_large_to_archive(rr)) { + return HeapShared::scratch_resolved_references(src_ik->constants()); + } + } + return nullptr; +} + void HeapShared::archive_java_mirrors() { for (int i = T_BOOLEAN; i < T_VOID+1; i++) { BasicType bt = (BasicType)i; if (!is_reference_type(bt)) { + oop orig_mirror = Universe::java_mirror(bt); oop m = _scratch_basic_type_mirrors[i].resolve(); assert(m != nullptr, "sanity"); - bool success = archive_reachable_objects_from(1, _default_subgraph_info, m); + copy_java_mirror_hashcode(orig_mirror, m); + bool success = archive_reachable_objects_from(1, _dump_time_special_subgraph, m); assert(success, "sanity"); log_trace(cds, heap, mirror)( @@ -418,12 +579,23 @@ void HeapShared::archive_java_mirrors() { GrowableArray* klasses = ArchiveBuilder::current()->klasses(); assert(klasses != nullptr, "sanity"); + for (int i = 0; i < klasses->length(); i++) { Klass* orig_k = klasses->at(i); + oop orig_mirror = orig_k->java_mirror(); + oop m = scratch_java_mirror(orig_k); + if (m != nullptr) { + copy_java_mirror_hashcode(orig_mirror, m); + } + } + + for (int i = 0; i < klasses->length(); i++) { + Klass* orig_k = klasses->at(i); + oop orig_mirror = orig_k->java_mirror(); oop m = scratch_java_mirror(orig_k); if (m != nullptr) { Klass* buffered_k = ArchiveBuilder::get_buffered_klass(orig_k); - bool success = archive_reachable_objects_from(1, _default_subgraph_info, m); + bool success = archive_reachable_objects_from(1, _dump_time_special_subgraph, m); guarantee(success, "scratch mirrors must point to only archivable objects"); buffered_k->set_archived_java_mirror(append_root(m)); ResourceMark rm; @@ -434,9 +606,9 @@ void HeapShared::archive_java_mirrors() { // archive the resolved_referenes array if (buffered_k->is_instance_klass()) { InstanceKlass* ik = InstanceKlass::cast(buffered_k); - oop rr = ik->constants()->prepare_resolved_references_for_archiving(); - if (rr != nullptr && !ArchiveHeapWriter::is_too_large_to_archive(rr)) { - bool success = HeapShared::archive_reachable_objects_from(1, _default_subgraph_info, rr); + objArrayOop rr = get_archived_resolved_references(InstanceKlass::cast(orig_k)); + if (rr != nullptr) { + bool success = HeapShared::archive_reachable_objects_from(1, _dump_time_special_subgraph, rr); assert(success, "must be"); int root_index = append_root(rr); ik->constants()->cache()->set_archived_references(root_index); @@ -448,7 +620,7 @@ void HeapShared::archive_java_mirrors() { void HeapShared::archive_strings() { oop shared_strings_array = StringTable::init_shared_table(_dumped_interned_strings); - bool success = archive_reachable_objects_from(1, _default_subgraph_info, shared_strings_array); + bool success = archive_reachable_objects_from(1, _dump_time_special_subgraph, shared_strings_array); // We must succeed because: // - _dumped_interned_strings do not contain any large strings. // - StringTable::init_shared_table() doesn't create any large arrays. @@ -457,7 +629,7 @@ void HeapShared::archive_strings() { } int HeapShared::archive_exception_instance(oop exception) { - bool success = archive_reachable_objects_from(1, _default_subgraph_info, exception); + bool success = archive_reachable_objects_from(1, _dump_time_special_subgraph, exception); assert(success, "sanity"); return append_root(exception); } @@ -466,6 +638,8 @@ void HeapShared::mark_native_pointers(oop orig_obj) { if (java_lang_Class::is_instance(orig_obj)) { ArchiveHeapWriter::mark_native_pointer(orig_obj, java_lang_Class::klass_offset()); ArchiveHeapWriter::mark_native_pointer(orig_obj, java_lang_Class::array_klass_offset()); + } else if (java_lang_invoke_ResolvedMethodName::is_instance(orig_obj)) { + ArchiveHeapWriter::mark_native_pointer(orig_obj, java_lang_invoke_ResolvedMethodName::vmtarget_offset()); } } @@ -482,11 +656,121 @@ void HeapShared::set_has_native_pointers(oop src_obj) { info->set_has_native_pointers(); } +void HeapShared::start_finding_required_hidden_classes() { + if (!CDSConfig::is_dumping_invokedynamic()) { + return; + } + NoSafepointVerifier nsv; + + init_seen_objects_table(); + + // We first scan the objects that are known to be archived (from the archive_subgraph + // tables) + find_required_hidden_classes_helper(archive_subgraph_entry_fields); + if (CDSConfig::is_dumping_full_module_graph()) { + find_required_hidden_classes_helper(fmg_archive_subgraph_entry_fields); + } + + // Later, SystemDictionaryShared::find_all_archivable_classes_impl() will start + // scanning the constant pools of all classes that it decides to archive. +} + +void HeapShared::end_finding_required_hidden_classes() { + if (!CDSConfig::is_dumping_invokedynamic()) { + return; + } + NoSafepointVerifier nsv; + + delete_seen_objects_table(); +} + +void HeapShared::find_required_hidden_classes_helper(ArchivableStaticFieldInfo fields[]) { + if (!CDSConfig::is_dumping_heap()) { + return; + } + for (int i = 0; fields[i].valid(); i++) { + ArchivableStaticFieldInfo* f = &fields[i]; + InstanceKlass* k = f->klass; + oop m = k->java_mirror(); + oop o = m->obj_field(f->offset); + if (o != nullptr) { + find_required_hidden_classes_in_object(o); + } + } +} + +class HeapShared::FindRequiredHiddenClassesOopClosure: public BasicOopIterateClosure { + GrowableArray _stack; + template void do_oop_work(T *p) { + // Recurse on a GrowableArray to avoid overflowing the C stack. + oop o = RawAccess<>::oop_load(p); + if (o != nullptr) { + _stack.append(o); + } + } + + public: + + void do_oop(narrowOop *p) { FindRequiredHiddenClassesOopClosure::do_oop_work(p); } + void do_oop( oop *p) { FindRequiredHiddenClassesOopClosure::do_oop_work(p); } + + FindRequiredHiddenClassesOopClosure(oop o) { + _stack.append(o); + } + oop pop() { + if (_stack.length() == 0) { + return nullptr; + } else { + return _stack.pop(); + } + } +}; + +static void mark_required_if_hidden_class(Klass* k) { + if (k != nullptr && k->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(k); + if (ik->is_hidden()) { + SystemDictionaryShared::mark_required_hidden_class(ik); + } + } +} + + +void HeapShared::find_required_hidden_classes_in_object(oop root) { + ResourceMark rm; + FindRequiredHiddenClassesOopClosure c(root); + oop o; + while ((o = c.pop()) != nullptr) { + if (!has_been_seen_during_subgraph_recording(o)) { + set_has_been_seen_during_subgraph_recording(o); + + // Mark the klass of this object + mark_required_if_hidden_class(o->klass()); + + // For special objects, mark the klass that they contain information about. + // - a Class that refers to an hidden class + // - a ResolvedMethodName that refers to a method declared in a hidden class + if (java_lang_Class::is_instance(o)) { + mark_required_if_hidden_class(java_lang_Class::as_Klass(o)); + } else if (java_lang_invoke_ResolvedMethodName::is_instance(o)) { + Method* m = java_lang_invoke_ResolvedMethodName::vmtarget(o); + if (m != nullptr) { + mark_required_if_hidden_class(m->method_holder()); + } + } + + o->oop_iterate(&c); + } + } +} + void HeapShared::archive_objects(ArchiveHeapInfo *heap_info) { { NoSafepointVerifier nsv; - _default_subgraph_info = init_subgraph_info(vmClasses::Object_klass(), false); + // The special subgraph doesn't belong to any class. We use Object_klass() here just + // for convenience. + _dump_time_special_subgraph = init_subgraph_info(vmClasses::Object_klass(), false); // Cache for recording where the archived objects are copied to create_archived_object_cache(); @@ -501,7 +785,7 @@ void HeapShared::archive_objects(ArchiveHeapInfo *heap_info) { copy_objects(); CDSHeapVerifier::verify(); - check_default_subgraph_classes(); + check_special_subgraph_classes(); } ArchiveHeapWriter::write(_pending_roots, heap_info); @@ -513,7 +797,7 @@ void HeapShared::copy_interned_strings() { auto copier = [&] (oop s, bool value_ignored) { assert(s != nullptr, "sanity"); assert(!ArchiveHeapWriter::is_string_too_large_to_archive(s), "large strings must have been filtered"); - bool success = archive_reachable_objects_from(1, _default_subgraph_info, s); + bool success = archive_reachable_objects_from(1, _dump_time_special_subgraph, s); assert(success, "must be"); // Prevent string deduplication from changing the value field to // something not in the archive. @@ -524,20 +808,35 @@ void HeapShared::copy_interned_strings() { delete_seen_objects_table(); } -void HeapShared::copy_special_objects() { - // Archive special objects that do not belong to any subgraphs +void HeapShared::copy_special_subgraph() { + copy_interned_strings(); + init_seen_objects_table(); - archive_java_mirrors(); - archive_strings(); - Universe::archive_exception_instances(); + { + archive_java_mirrors(); + archive_strings(); + Universe::archive_exception_instances(); + } delete_seen_objects_table(); } +void HeapShared::prepare_resolved_references() { + GrowableArray* klasses = ArchiveBuilder::current()->klasses(); + for (int i = 0; i < klasses->length(); i++) { + Klass* src_k = klasses->at(i); + if (src_k->is_instance_klass()) { + InstanceKlass* buffered_ik = ArchiveBuilder::current()->get_buffered_addr(InstanceKlass::cast(src_k)); + buffered_ik->constants()->prepare_resolved_references_for_archiving(); + } + } +} + void HeapShared::copy_objects() { assert(HeapShared::can_write(), "must be"); - copy_interned_strings(); - copy_special_objects(); + prepare_resolved_references(); + find_all_aot_initialized_classes(); + copy_special_subgraph(); archive_object_subgraphs(archive_subgraph_entry_fields, false /* is_full_module_graph */); @@ -549,6 +848,198 @@ void HeapShared::copy_objects() { } } +// Closure used by HeapShared::scan_for_aot_initialized_classes() to look for all objects +// that are reachable from a given root. +class HeapShared::AOTInitializedClassScanner : public BasicOopIterateClosure { + bool _made_progress; + + template void check(T *p) { + oop obj = HeapAccess<>::oop_load(p); + if (!java_lang_Class::is_instance(obj)) { + // Don't scan the mirrors, as we may see an orig_mirror while scanning + // the object graph, .... TODO more info + _made_progress |= HeapShared::scan_for_aot_initialized_classes(obj); + } + } + +public: + AOTInitializedClassScanner() : _made_progress(false) {} + void do_oop(narrowOop *p) { check(p); } + void do_oop( oop *p) { check(p); } + bool made_progress() { return _made_progress; } +}; + +// If has been initialized during the assembly phase, mark its +// has_aot_initialized_mirror bit. And then do the same for all supertypes of +// . +// +// Note: a super interface of may not have been initialized, if +// has not declared any default methods. +// +// Note: this function doesn not call InstanceKlass::initialize() -- we are inside +// a safepoint. +// +// Returns true if one or more classes have been newly marked. +static bool mark_for_aot_initialization(InstanceKlass* buffered_ik) { + assert(SafepointSynchronize::is_at_safepoint(), "sanity"); + assert(ArchiveBuilder::current()->is_in_buffer_space(buffered_ik), "sanity"); + + if (buffered_ik->has_aot_initialized_mirror()) { // already marked + return false; + } + + bool made_progress = false; + if (buffered_ik->is_initialized()) { + if (log_is_enabled(Info, cds, init)) { + ResourceMark rm; + log_info(cds, init)("Mark class for aot-init: %s", buffered_ik->external_name()); + } + + InstanceKlass* src_ik = ArchiveBuilder::current()->get_source_addr(buffered_ik); + + // If we get here with a "wild" user class, which may have + // uncontrolled code, exit with an error. Obviously + // filtering logic upstream needs to detect APP classes and not mark + // them for aot-init in the first place, but this will be the final + // firewall. + +#ifndef PRODUCT + // ArchiveHeapTestClass is used for a very small number of internal regression + // tests (non-product builds only). It may initialize some unexpected classes. + if (ArchiveHeapTestClass == nullptr) +#endif + { + if (!src_ik->in_javabase_module()) { + // Class/interface types in the boot loader may have been initialized as side effects + // of JVM bootstrap code, so they are fine. But we need to check all other classes. + if (buffered_ik->is_interface()) { + // This probably means a bug in AOTConstantPoolResolver.::is_indy_resolution_deterministic() + guarantee(!buffered_ik->interface_needs_clinit_execution_as_super(), + "should not have initialized an interface whose might have unpredictable side effects"); + } else { + // "normal" classes + guarantee(HeapShared::is_archivable_hidden_klass(buffered_ik), + "should not have initialized any non-interface, non-hidden classes outside of java.base"); + } + } + } + + buffered_ik->set_has_aot_initialized_mirror(); + if (AOTClassInitializer::is_runtime_setup_required(src_ik)) { + buffered_ik->set_is_runtime_setup_required(); + } + made_progress = true; + + InstanceKlass* super = buffered_ik->java_super(); + if (super != nullptr) { + mark_for_aot_initialization(super); + } + + Array* interfaces = buffered_ik->transitive_interfaces(); + for (int i = 0; i < interfaces->length(); i++) { + InstanceKlass* intf = interfaces->at(i); + mark_for_aot_initialization(intf); + if (!intf->is_initialized()) { + assert(!intf->interface_needs_clinit_execution_as_super(/*also_check_supers*/false), "sanity"); + assert(!intf->has_aot_initialized_mirror(), "must not be marked"); + } + } + } + + return made_progress; +} + +void HeapShared::find_all_aot_initialized_classes() { + if (!CDSConfig::is_dumping_aot_linked_classes()) { + return; + } + + init_seen_objects_table(); + find_all_aot_initialized_classes_helper(); + delete_seen_objects_table(); +} + +// Recursively find all class that should be aot-initialized: +// - the class has at least one instance that can be reachable from the special subgraph; or +// - the class is hard-coded in AOTClassInitializer::can_archive_initialized_mirror() +void HeapShared::find_all_aot_initialized_classes_helper() { + GrowableArray* klasses = ArchiveBuilder::current()->klasses(); + assert(klasses != nullptr, "sanity"); + + // First scan all resolved constant pools references. + for (int i = 0; i < klasses->length(); i++) { + Klass* src_k = klasses->at(i); + if (src_k->is_instance_klass()) { + InstanceKlass* src_ik = InstanceKlass::cast(src_k); + InstanceKlass* buffered_ik = ArchiveBuilder::current()->get_buffered_addr(src_ik); + objArrayOop rr = get_archived_resolved_references(src_ik); + if (rr != nullptr) { + objArrayOop scratch_rr = scratch_resolved_references(src_ik->constants()); + for (int i = 0; i < scratch_rr->length(); i++) { + scan_for_aot_initialized_classes(scratch_rr->obj_at(i)); + } + } + + // If a class is hard-coded to be aot-initialize, mark it as such. + if (AOTClassInitializer::can_archive_initialized_mirror(src_ik)) { + mark_for_aot_initialization(buffered_ik); + } + } + } + + // These objects also belong to the special subgraph + scan_for_aot_initialized_classes(Universe::null_ptr_exception_instance()); + scan_for_aot_initialized_classes(Universe::arithmetic_exception_instance()); + scan_for_aot_initialized_classes(Universe::internal_error_instance()); + scan_for_aot_initialized_classes(Universe::array_index_out_of_bounds_exception_instance()); + scan_for_aot_initialized_classes(Universe::array_store_exception_instance()); + scan_for_aot_initialized_classes(Universe::class_cast_exception_instance()); + + bool made_progress; + do { + // In each pass, we copy the scratch mirrors of the classes that were marked + // as aot-init in the previous pass. We then scan these mirrors, which may + // mark more classes. Keep iterating until no more progress can be made. + made_progress = false; + for (int i = 0; i < klasses->length(); i++) { + Klass* orig_k = klasses->at(i); + if (orig_k->is_instance_klass()) { + InstanceKlass* orig_ik = InstanceKlass::cast(orig_k); + if (ArchiveBuilder::current()->get_buffered_addr(orig_ik)->has_aot_initialized_mirror()) { + oop orig_mirror = orig_ik->java_mirror(); + oop scratch_mirror = scratch_java_mirror(orig_k); + if (!has_been_seen_during_subgraph_recording(scratch_mirror)) { + // Scan scratch_mirror instead of orig_mirror (which has fields like ClassLoader that + // are not archived). + copy_aot_initialized_mirror(orig_k, orig_mirror, scratch_mirror); + made_progress |= scan_for_aot_initialized_classes(scratch_mirror); + } + } + } + } + } while (made_progress); +} + +bool HeapShared::scan_for_aot_initialized_classes(oop obj) { + if (obj == nullptr || has_been_seen_during_subgraph_recording(obj)) { + return false; + } + set_has_been_seen_during_subgraph_recording(obj); + + bool made_progress = false; + Klass* k = obj->klass(); + if (k->is_instance_klass()) { + InstanceKlass* orig_ik = InstanceKlass::cast(k); + InstanceKlass* buffered_ik = ArchiveBuilder::current()->get_buffered_addr(orig_ik); + made_progress = mark_for_aot_initialization(buffered_ik); + } + + AOTInitializedClassScanner scanner; + obj->oop_iterate(&scanner); + made_progress |= scanner.made_progress(); + return made_progress; +} + // // Subgraph archiving support // @@ -607,8 +1098,14 @@ void KlassSubGraphInfo::add_subgraph_object_klass(Klass* orig_k) { } if (buffered_k->is_instance_klass()) { - assert(InstanceKlass::cast(buffered_k)->is_shared_boot_class(), - "must be boot class"); + if (CDSConfig::is_dumping_invokedynamic()) { + assert(InstanceKlass::cast(buffered_k)->is_shared_boot_class() || + HeapShared::is_lambda_proxy_klass(InstanceKlass::cast(buffered_k)), + "we can archive only instances of boot classes or lambda proxy classes"); + } else { + assert(InstanceKlass::cast(buffered_k)->is_shared_boot_class(), + "must be boot class"); + } // vmClasses::xxx_klass() are not updated, need to check // the original Klass* if (orig_k == vmClasses::String_klass() || @@ -617,6 +1114,10 @@ void KlassSubGraphInfo::add_subgraph_object_klass(Klass* orig_k) { // to the sub-graph object class list. return; } + if (buffered_k->has_aot_initialized_mirror()) { + // No need to add to the runtime-init list. + return; + } check_allowed_klass(InstanceKlass::cast(orig_k)); } else if (buffered_k->is_objArray_klass()) { Klass* abk = ObjArrayKlass::cast(buffered_k)->bottom_klass(); @@ -653,19 +1154,30 @@ void KlassSubGraphInfo::check_allowed_klass(InstanceKlass* ik) { return; } + const char* lambda_msg = ""; + if (CDSConfig::is_dumping_invokedynamic()) { + lambda_msg = ", or a lambda proxy class"; + if (HeapShared::is_lambda_proxy_klass(ik) && + (ik->class_loader() == nullptr || + ik->class_loader() == SystemDictionary::java_platform_loader() || + ik->class_loader() == SystemDictionary::java_system_loader())) { + return; + } + } + #ifndef PRODUCT - if (!ik->module()->is_named() && ik->package() == nullptr) { + if (!ik->module()->is_named() && ik->package() == nullptr && ArchiveHeapTestClass != nullptr) { // This class is loaded by ArchiveHeapTestClass return; } - const char* extra_msg = ", or in an unnamed package of an unnamed module"; + const char* testcls_msg = ", or a test class in an unnamed package of an unnamed module"; #else - const char* extra_msg = ""; + const char* testcls_msg = ""; #endif ResourceMark rm; - log_error(cds, heap)("Class %s not allowed in archive heap. Must be in java.base%s", - ik->external_name(), extra_msg); + log_error(cds, heap)("Class %s not allowed in archive heap. Must be in java.base%s%s", + ik->external_name(), lambda_msg, testcls_msg); MetaspaceShared::unrecoverable_writing_error(); } @@ -727,13 +1239,18 @@ void ArchivedKlassSubGraphInfoRecord::init(KlassSubGraphInfo* info) { int num_subgraphs_klasses = subgraph_object_klasses->length(); _subgraph_object_klasses = ArchiveBuilder::new_ro_array(num_subgraphs_klasses); + bool is_special = (_k == ArchiveBuilder::get_buffered_klass(vmClasses::Object_klass())); for (int i = 0; i < num_subgraphs_klasses; i++) { Klass* subgraph_k = subgraph_object_klasses->at(i); if (log_is_enabled(Info, cds, heap)) { ResourceMark rm; + const char* owner_name = is_special ? "" : _k->external_name(); + if (subgraph_k->is_instance_klass()) { + InstanceKlass* src_ik = InstanceKlass::cast(ArchiveBuilder::current()->get_source_addr(subgraph_k)); + } log_info(cds, heap)( "Archived object klass %s (%2d) => %s", - _k->external_name(), i, subgraph_k->external_name()); + owner_name, i, subgraph_k->external_name()); } _subgraph_object_klasses->at_put(i, subgraph_k); ArchivePtrMarker::mark_pointer(_subgraph_object_klasses->adr_at(i)); @@ -745,16 +1262,14 @@ void ArchivedKlassSubGraphInfoRecord::init(KlassSubGraphInfo* info) { ArchivePtrMarker::mark_pointer(&_subgraph_object_klasses); } -struct CopyKlassSubGraphInfoToArchive : StackObj { +class HeapShared::CopyKlassSubGraphInfoToArchive : StackObj { CompactHashtableWriter* _writer; +public: CopyKlassSubGraphInfoToArchive(CompactHashtableWriter* writer) : _writer(writer) {} bool do_entry(Klass* klass, KlassSubGraphInfo& info) { if (info.subgraph_object_klasses() != nullptr || info.subgraph_entry_fields() != nullptr) { - ArchivedKlassSubGraphInfoRecord* record = - (ArchivedKlassSubGraphInfoRecord*)ArchiveBuilder::ro_region_alloc(sizeof(ArchivedKlassSubGraphInfoRecord)); - record->init(&info); - + ArchivedKlassSubGraphInfoRecord* record = HeapShared::archive_subgraph_info(&info); Klass* buffered_k = ArchiveBuilder::get_buffered_klass(klass); unsigned int hash = SystemDictionaryShared::hash_for_shared_dictionary((address)buffered_k); u4 delta = ArchiveBuilder::current()->any_to_offset_u4(record); @@ -764,6 +1279,16 @@ struct CopyKlassSubGraphInfoToArchive : StackObj { } }; +ArchivedKlassSubGraphInfoRecord* HeapShared::archive_subgraph_info(KlassSubGraphInfo* info) { + ArchivedKlassSubGraphInfoRecord* record = + (ArchivedKlassSubGraphInfoRecord*)ArchiveBuilder::ro_region_alloc(sizeof(ArchivedKlassSubGraphInfoRecord)); + record->init(info); + if (info == _dump_time_special_subgraph) { + _run_time_special_subgraph = record; + } + return record; +} + // Build the records of archived subgraph infos, which include: // - Entry points to all subgraphs from the containing class mirror. The entry // points are static fields in the mirror. For each entry point, the field @@ -820,6 +1345,7 @@ void HeapShared::serialize_tables(SerializeClosure* soc) { #endif _run_time_subgraph_info_table.serialize_header(soc); + soc->do_ptr(&_run_time_special_subgraph); } static void verify_the_heap(Klass* k, const char* which) { @@ -887,6 +1413,65 @@ void HeapShared::resolve_classes_for_subgraph_of(JavaThread* current, Klass* k) } } +void HeapShared::initialize_java_lang_invoke(TRAPS) { + if (CDSConfig::is_loading_invokedynamic() || CDSConfig::is_dumping_invokedynamic()) { + resolve_or_init("java/lang/invoke/Invokers$Holder", true, CHECK); + resolve_or_init("java/lang/invoke/MethodHandle", true, CHECK); + resolve_or_init("java/lang/invoke/MethodHandleNatives", true, CHECK); + resolve_or_init("java/lang/invoke/DirectMethodHandle$Holder", true, CHECK); + resolve_or_init("java/lang/invoke/DelegatingMethodHandle$Holder", true, CHECK); + resolve_or_init("java/lang/invoke/LambdaForm$Holder", true, CHECK); + resolve_or_init("java/lang/invoke/BoundMethodHandle$Species_L", true, CHECK); + } +} + +// Initialize the InstanceKlasses of objects that are reachable from the following roots: +// - interned strings +// - Klass::java_mirror() -- including aot-initialized mirrors such as those of Enum klasses. +// - ConstantPool::resolved_references() +// - Universe::_exception_instance() +// +// For example, if this enum class is initialized at AOT cache assembly time: +// +// enum Fruit { +// APPLE, ORANGE, BANANA; +// static final Set HAVE_SEEDS = new HashSet<>(Arrays.asList(APPLE, ORANGE)); +// } +// +// the aot-initialized mirror of Fruit has a static field that references HashSet, which +// should be initialized before any Java code can access the Fruit class. Note that +// HashSet itself doesn't necessary need to be an aot-initialized class. +void HeapShared::init_classes_for_special_subgraph(Handle class_loader, TRAPS) { + if (!ArchiveHeapLoader::is_in_use()) { + return; + } + + assert( _run_time_special_subgraph != nullptr, "must be"); + Array* klasses = _run_time_special_subgraph->subgraph_object_klasses(); + if (klasses != nullptr) { + for (int pass = 0; pass < 2; pass ++) { + for (int i = 0; i < klasses->length(); i++) { + Klass* k = klasses->at(i); + if (k->class_loader_data() == nullptr) { + // This class is not yet loaded. We will initialize it in a later phase. + // For example, we have loaded only AOTLinkedClassCategory::BOOT1 classes + // but k is part of AOTLinkedClassCategory::BOOT2. + continue; + } + if (k->class_loader() == class_loader()) { + if (pass == 0) { + if (k->is_instance_klass()) { + InstanceKlass::cast(k)->link_class(CHECK); + } + } else { + resolve_or_init(k, /*do_init*/true, CHECK); + } + } + } + } + } +} + void HeapShared::initialize_from_archived_subgraph(JavaThread* current, Klass* k) { JavaThread* THREAD = current; if (!ArchiveHeapLoader::is_in_use()) { @@ -990,6 +1575,19 @@ HeapShared::resolve_or_init_classes_for_subgraph_of(Klass* k, bool do_init, TRAP return record; } +void HeapShared::resolve_or_init(const char* klass_name, bool do_init, TRAPS) { + TempNewSymbol klass_name_sym = SymbolTable::new_symbol(klass_name); + InstanceKlass* k = SystemDictionaryShared::find_builtin_class(klass_name_sym); + if (k == nullptr) { + return; + } + assert(k->is_shared_boot_class(), "sanity"); + resolve_or_init(k, false, CHECK); + if (do_init) { + resolve_or_init(k, true, CHECK); + } +} + void HeapShared::resolve_or_init(Klass* k, bool do_init, TRAPS) { if (!do_init) { if (k->class_loader_data() == nullptr) { @@ -1022,7 +1620,11 @@ void HeapShared::init_archived_fields_for(Klass* k, const ArchivedKlassSubGraphI int field_offset = entry_field_records->at(i); int root_index = entry_field_records->at(i+1); oop v = get_root(root_index, /*clear=*/true); - m->obj_field_put(field_offset, v); + if (k->has_aot_initialized_mirror()) { + assert(v == m->obj_field(field_offset), "must be aot-initialized"); + } else { + m->obj_field_put(field_offset, v); + } log_debug(cds, heap)(" " PTR_FORMAT " init field @ %2d = " PTR_FORMAT, p2i(k), field_offset, p2i(v)); } @@ -1030,8 +1632,9 @@ void HeapShared::init_archived_fields_for(Klass* k, const ArchivedKlassSubGraphI // mirror after this point. if (log_is_enabled(Info, cds, heap)) { ResourceMark rm; - log_info(cds, heap)("initialize_from_archived_subgraph %s " PTR_FORMAT "%s", - k->external_name(), p2i(k), JvmtiExport::is_early_phase() ? " (early)" : ""); + log_info(cds, heap)("initialize_from_archived_subgraph %s " PTR_FORMAT "%s%s", + k->external_name(), p2i(k), JvmtiExport::is_early_phase() ? " (early)" : "", + k->has_aot_initialized_mirror() ? " (aot-inited)" : ""); } } @@ -1137,6 +1740,20 @@ HeapShared::CachedOopInfo HeapShared::make_cached_oop_info(oop obj) { return CachedOopInfo(referrer, points_to_oops_checker.result()); } +void HeapShared::init_box_classes(TRAPS) { + if (ArchiveHeapLoader::is_in_use()) { + vmClasses::Boolean_klass()->initialize(CHECK); + vmClasses::Character_klass()->initialize(CHECK); + vmClasses::Float_klass()->initialize(CHECK); + vmClasses::Double_klass()->initialize(CHECK); + vmClasses::Byte_klass()->initialize(CHECK); + vmClasses::Short_klass()->initialize(CHECK); + vmClasses::Integer_klass()->initialize(CHECK); + vmClasses::Long_klass()->initialize(CHECK); + vmClasses::Void_klass()->initialize(CHECK); + } +} + // (1) If orig_obj has not been archived yet, archive it. // (2) If orig_obj has not been seen yet (since start_recording_subgraph() was called), // trace all objects that are reachable from it, and make sure these objects are archived. @@ -1151,25 +1768,56 @@ bool HeapShared::archive_reachable_objects_from(int level, // If you get an error here, you probably made a change in the JDK library that has added // these objects that are referenced (directly or indirectly) by static fields. ResourceMark rm; - log_error(cds, heap)("Cannot archive object of class %s", orig_obj->klass()->external_name()); - if (log_is_enabled(Trace, cds, heap)) { - WalkOopAndArchiveClosure* walker = WalkOopAndArchiveClosure::current(); - if (walker != nullptr) { - LogStream ls(Log(cds, heap)::trace()); - CDSHeapVerifier::trace_to_root(&ls, walker->referencing_obj()); - } - } + log_error(cds, heap)("Cannot archive object " PTR_FORMAT " of class %s", p2i(orig_obj), orig_obj->klass()->external_name()); + debug_trace(); MetaspaceShared::unrecoverable_writing_error(); } - // java.lang.Class instances cannot be included in an archived object sub-graph. We only support - // them as Klass::_archived_mirror because they need to be specially restored at run time. - // - // If you get an error here, you probably made a change in the JDK library that has added a Class - // object that is referenced (directly or indirectly) by static fields. - if (java_lang_Class::is_instance(orig_obj) && subgraph_info != _default_subgraph_info) { - log_error(cds, heap)("(%d) Unknown java.lang.Class object is in the archived sub-graph", level); - MetaspaceShared::unrecoverable_writing_error(); + if (log_is_enabled(Debug, cds, heap) && java_lang_Class::is_instance(orig_obj)) { + ResourceMark rm; + LogTarget(Debug, cds, heap) log; + LogStream out(log); + out.print("Found java mirror " PTR_FORMAT " ", p2i(orig_obj)); + Klass* k = java_lang_Class::as_Klass(orig_obj); + if (k != nullptr) { + out.print("%s", k->external_name()); + } else { + out.print("primitive"); + } + out.print_cr("; scratch mirror = " PTR_FORMAT, + p2i(scratch_java_mirror(orig_obj))); + } + + if (CDSConfig::is_initing_classes_at_dump_time()) { + if (java_lang_Class::is_instance(orig_obj)) { + orig_obj = scratch_java_mirror(orig_obj); + assert(orig_obj != nullptr, "must be archived"); + } + } else if (java_lang_Class::is_instance(orig_obj) && subgraph_info != _dump_time_special_subgraph) { + // Without CDSConfig::is_initing_classes_at_dump_time(), we only allow archived objects to + // point to the mirrors of (1) j.l.Object, (2) primitive classes, and (3) box classes. These are initialized + // very early by HeapShared::init_box_classes(). + if (orig_obj == vmClasses::Object_klass()->java_mirror() + || java_lang_Class::is_primitive(orig_obj) + || orig_obj == vmClasses::Boolean_klass()->java_mirror() + || orig_obj == vmClasses::Character_klass()->java_mirror() + || orig_obj == vmClasses::Float_klass()->java_mirror() + || orig_obj == vmClasses::Double_klass()->java_mirror() + || orig_obj == vmClasses::Byte_klass()->java_mirror() + || orig_obj == vmClasses::Short_klass()->java_mirror() + || orig_obj == vmClasses::Integer_klass()->java_mirror() + || orig_obj == vmClasses::Long_klass()->java_mirror() + || orig_obj == vmClasses::Void_klass()->java_mirror()) { + orig_obj = scratch_java_mirror(orig_obj); + assert(orig_obj != nullptr, "must be archived"); + } else { + // If you get an error here, you probably made a change in the JDK library that has added a Class + // object that is referenced (directly or indirectly) by an ArchivableStaticFieldInfo + // defined at the top of this file. + log_error(cds, heap)("(%d) Unknown java.lang.Class object is in the archived sub-graph", level); + debug_trace(); + MetaspaceShared::unrecoverable_writing_error(); + } } if (has_been_seen_during_subgraph_recording(orig_obj)) { @@ -1209,9 +1857,15 @@ bool HeapShared::archive_reachable_objects_from(int level, WalkOopAndArchiveClosure walker(level, record_klasses_only, subgraph_info, orig_obj); orig_obj->oop_iterate(&walker); - if (CDSEnumKlass::is_enum_obj(orig_obj)) { - CDSEnumKlass::handle_enum_obj(level + 1, subgraph_info, orig_obj); + if (CDSConfig::is_initing_classes_at_dump_time()) { + // The enum klasses are archived with aot-initialized mirror. + // See AOTClassInitializer::can_archive_initialized_mirror(). + } else { + if (CDSEnumKlass::is_enum_obj(orig_obj)) { + CDSEnumKlass::handle_enum_obj(level + 1, subgraph_info, orig_obj); + } } + return true; } @@ -1329,6 +1983,10 @@ void HeapShared::verify_subgraph_from(oop orig_obj) { void HeapShared::verify_reachable_objects_from(oop obj) { _num_total_verifications ++; + if (java_lang_Class::is_instance(obj)) { + obj = scratch_java_mirror(obj); + assert(obj != nullptr, "must be"); + } if (!has_been_seen_during_subgraph_recording(obj)) { set_has_been_seen_during_subgraph_recording(obj); assert(has_been_archived(obj), "must be"); @@ -1338,35 +1996,35 @@ void HeapShared::verify_reachable_objects_from(oop obj) { } #endif -// The "default subgraph" contains special objects (see heapShared.hpp) that -// can be accessed before we load any Java classes (including java/lang/Class). -// Make sure that these are only instances of the very few specific types -// that we can handle. -void HeapShared::check_default_subgraph_classes() { - GrowableArray* klasses = _default_subgraph_info->subgraph_object_klasses(); - int num = klasses->length(); - for (int i = 0; i < num; i++) { - Klass* subgraph_k = klasses->at(i); - if (log_is_enabled(Info, cds, heap)) { - ResourceMark rm; - log_info(cds, heap)( - "Archived object klass (default subgraph %d) => %s", - i, subgraph_k->external_name()); +void HeapShared::check_special_subgraph_classes() { + if (CDSConfig::is_initing_classes_at_dump_time()) { + // We can have aot-initialized classes (such as Enums) that can reference objects + // of arbitrary types. Currently, we trust the JEP 483 implementation to only + // aot-initialize classes that are "safe". + // + // TODO: we need an automatic tool that checks the safety of aot-initialized + // classes (when we extend the set of aot-initialized classes beyond JEP 483) + return; + } else { + // In this case, the special subgraph should contain a few specific types + GrowableArray* klasses = _dump_time_special_subgraph->subgraph_object_klasses(); + int num = klasses->length(); + for (int i = 0; i < num; i++) { + Klass* subgraph_k = klasses->at(i); + Symbol* name = ArchiveBuilder::current()->get_source_addr(subgraph_k->name()); + if (subgraph_k->is_instance_klass() && + name != vmSymbols::java_lang_Class() && + name != vmSymbols::java_lang_String() && + name != vmSymbols::java_lang_ArithmeticException() && + name != vmSymbols::java_lang_ArrayIndexOutOfBoundsException() && + name != vmSymbols::java_lang_ArrayStoreException() && + name != vmSymbols::java_lang_ClassCastException() && + name != vmSymbols::java_lang_InternalError() && + name != vmSymbols::java_lang_NullPointerException()) { + ResourceMark rm; + fatal("special subgraph cannot have objects of type %s", subgraph_k->external_name()); + } } - - Symbol* name = ArchiveBuilder::current()->get_source_addr(subgraph_k->name()); - guarantee(name == vmSymbols::java_lang_Class() || - name == vmSymbols::java_lang_String() || - name == vmSymbols::java_lang_ArithmeticException() || - name == vmSymbols::java_lang_NullPointerException() || - name == vmSymbols::java_lang_InternalError() || - name == vmSymbols::java_lang_ArrayIndexOutOfBoundsException() || - name == vmSymbols::java_lang_ArrayStoreException() || - name == vmSymbols::java_lang_ClassCastException() || - name == vmSymbols::object_array_signature() || - name == vmSymbols::byte_array_signature() || - name == vmSymbols::char_array_signature(), - "default subgraph can have only these objects"); } } @@ -1572,8 +2230,8 @@ bool HeapShared::is_a_test_class_in_unnamed_module(Klass* ik) { return false; } - // See KlassSubGraphInfo::check_allowed_klass() - only two types of - // classes are allowed: + // See KlassSubGraphInfo::check_allowed_klass() - we only allow test classes + // to be: // (A) java.base classes (which must not be in the unnamed module) // (B) test classes which must be in the unnamed package of the unnamed module. // So if we see a '/' character in the class name, it must be in (A); @@ -1589,6 +2247,25 @@ bool HeapShared::is_a_test_class_in_unnamed_module(Klass* ik) { return false; } + +void HeapShared::initialize_test_class_from_archive(JavaThread* current) { + Klass* k = _test_class; + if (k != nullptr && ArchiveHeapLoader::is_in_use()) { + JavaThread* THREAD = current; + ExceptionMark em(THREAD); + const ArchivedKlassSubGraphInfoRecord* record = + resolve_or_init_classes_for_subgraph_of(k, /*do_init=*/false, THREAD); + + // The _test_class is in the unnamed module, so it can't call CDS.initializeFromArchive() + // from its method. So we set up its "archivedObjects" field first, before + // calling its . This is not strictly clean, but it's a convenient way to write unit + // test cases (see test/hotspot/jtreg/runtime/cds/appcds/cacheObject/ArchiveHeapTestClass.java). + if (record != nullptr) { + init_archived_fields_for(k, record); + } + resolve_or_init_classes_for_subgraph_of(k, /*do_init=*/true, THREAD); + } +} #endif void HeapShared::init_for_dumping(TRAPS) { @@ -1664,6 +2341,15 @@ void HeapShared::add_to_dumped_interned_strings(oop string) { } } +void HeapShared::debug_trace() { + ResourceMark rm; + WalkOopAndArchiveClosure* walker = WalkOopAndArchiveClosure::current(); + if (walker != nullptr) { + LogStream ls(Log(cds, heap)::error()); + CDSHeapVerifier::trace_to_root(&ls, walker->referencing_obj()); + } +} + #ifndef PRODUCT // At dump-time, find the location of all the non-null oop pointers in an archived heap // region. This way we can quickly relocate all the pointers without using diff --git a/src/hotspot/share/cds/heapShared.hpp b/src/hotspot/share/cds/heapShared.hpp index 317b791f5d3..618adeb308f 100644 --- a/src/hotspot/share/cds/heapShared.hpp +++ b/src/hotspot/share/cds/heapShared.hpp @@ -166,10 +166,16 @@ public: static bool is_subgraph_root_class(InstanceKlass* ik); // Scratch objects for archiving Klass::java_mirror() - static oop scratch_java_mirror(BasicType t) NOT_CDS_JAVA_HEAP_RETURN_(nullptr); - static oop scratch_java_mirror(Klass* k) NOT_CDS_JAVA_HEAP_RETURN_(nullptr); + static oop scratch_java_mirror(BasicType t) NOT_CDS_JAVA_HEAP_RETURN_(nullptr); + static oop scratch_java_mirror(Klass* k) NOT_CDS_JAVA_HEAP_RETURN_(nullptr); + static oop scratch_java_mirror(oop java_mirror) NOT_CDS_JAVA_HEAP_RETURN_(nullptr); static bool is_archived_boot_layer_available(JavaThread* current) NOT_CDS_JAVA_HEAP_RETURN_(false); + // Look for all hidden classes that are referenced by archived objects. + static void start_finding_required_hidden_classes() NOT_CDS_JAVA_HEAP_RETURN; + static void find_required_hidden_classes_in_object(oop o) NOT_CDS_JAVA_HEAP_RETURN; + static void end_finding_required_hidden_classes() NOT_CDS_JAVA_HEAP_RETURN; + private: #if INCLUDE_CDS_JAVA_HEAP static bool _disable_writing; @@ -184,12 +190,15 @@ private: static void count_allocation(size_t size); static void print_stats(); + static void debug_trace(); public: static unsigned oop_hash(oop const& p); static unsigned string_oop_hash(oop const& string) { return java_lang_String::hash_code(string); } + class CopyKlassSubGraphInfoToArchive; + class CachedOopInfo { // Used by CDSHeapVerifier. oop _orig_referrer; @@ -251,7 +260,11 @@ private: static DumpTimeKlassSubGraphInfoTable* _dump_time_subgraph_info_table; static RunTimeKlassSubGraphInfoTable _run_time_subgraph_info_table; + class FindRequiredHiddenClassesOopClosure; + static void find_required_hidden_classes_helper(ArchivableStaticFieldInfo fields[]); + static CachedOopInfo make_cached_oop_info(oop obj); + static ArchivedKlassSubGraphInfoRecord* archive_subgraph_info(KlassSubGraphInfo* info); static void archive_object_subgraphs(ArchivableStaticFieldInfo fields[], bool is_full_module_graph); @@ -265,7 +278,7 @@ private: InstanceKlass* k, int field_offset) PRODUCT_RETURN; static void verify_reachable_objects_from(oop obj) PRODUCT_RETURN; static void verify_subgraph_from(oop orig_obj) PRODUCT_RETURN; - static void check_default_subgraph_classes(); + static void check_special_subgraph_classes(); static KlassSubGraphInfo* init_subgraph_info(Klass *k, bool is_full_module_graph); static KlassSubGraphInfo* get_subgraph_info(Klass *k); @@ -287,12 +300,14 @@ private: static SeenObjectsTable *_seen_objects_table; - // The "default subgraph" is the root of all archived objects that do not belong to any - // of the classes defined in the _archive_subgraph_entry_fields[] arrays: + // The "special subgraph" contains all the archived objects that are reachable + // from the following roots: // - interned strings - // - Klass::java_mirror() + // - Klass::java_mirror() -- including aot-initialized mirrors such as those of Enum klasses. // - ConstantPool::resolved_references() - static KlassSubGraphInfo* _default_subgraph_info; + // - Universe::_exception_instance() + static KlassSubGraphInfo* _dump_time_special_subgraph; // for collecting info during dump time + static ArchivedKlassSubGraphInfoRecord* _run_time_special_subgraph; // for initializing classes during run time. static GrowableArrayCHeap* _pending_roots; static GrowableArrayCHeap* _root_segments; @@ -330,7 +345,7 @@ private: static bool has_been_seen_during_subgraph_recording(oop obj); static void set_has_been_seen_during_subgraph_recording(oop obj); static bool archive_object(oop obj); - + static void copy_aot_initialized_mirror(Klass* orig_k, oop orig_mirror, oop m); static void copy_interned_strings(); static void resolve_classes_for_subgraphs(JavaThread* current, ArchivableStaticFieldInfo fields[]); @@ -338,6 +353,7 @@ private: static void clear_archived_roots_of(Klass* k); static const ArchivedKlassSubGraphInfoRecord* resolve_or_init_classes_for_subgraph_of(Klass* k, bool do_init, TRAPS); + static void resolve_or_init(const char* klass_name, bool do_init, TRAPS); static void resolve_or_init(Klass* k, bool do_init, TRAPS); static void init_archived_fields_for(Klass* k, const ArchivedKlassSubGraphInfoRecord* record); @@ -352,8 +368,16 @@ private: static void fill_failed_loaded_region(); static void mark_native_pointers(oop orig_obj); static bool has_been_archived(oop orig_obj); + static void prepare_resolved_references(); static void archive_java_mirrors(); static void archive_strings(); + static void copy_special_subgraph(); + + class AOTInitializedClassScanner; + static void find_all_aot_initialized_classes(); + static void find_all_aot_initialized_classes_helper(); + static bool scan_for_aot_initialized_classes(oop obj); + public: static void reset_archived_object_states(TRAPS); static void create_archived_object_cache() { @@ -371,7 +395,6 @@ private: static int archive_exception_instance(oop exception); static void archive_objects(ArchiveHeapInfo* heap_info); static void copy_objects(); - static void copy_special_objects(); static bool archive_reachable_objects_from(int level, KlassSubGraphInfo* subgraph_info, @@ -420,6 +443,7 @@ private: static objArrayOop scratch_resolved_references(ConstantPool* src); static void add_scratch_resolved_references(ConstantPool* src, objArrayOop dest) NOT_CDS_JAVA_HEAP_RETURN; static void init_scratch_objects(TRAPS) NOT_CDS_JAVA_HEAP_RETURN; + static void init_box_classes(TRAPS) NOT_CDS_JAVA_HEAP_RETURN; static bool is_heap_region(int idx) { CDS_JAVA_HEAP_ONLY(return (idx == MetaspaceShared::hp);) NOT_CDS_JAVA_HEAP_RETURN_(false); @@ -436,7 +460,16 @@ private: #ifndef PRODUCT static bool is_a_test_class_in_unnamed_module(Klass* ik) NOT_CDS_JAVA_HEAP_RETURN_(false); + static void initialize_test_class_from_archive(TRAPS) NOT_CDS_JAVA_HEAP_RETURN; #endif + + static void initialize_java_lang_invoke(TRAPS) NOT_CDS_JAVA_HEAP_RETURN; + static void init_classes_for_special_subgraph(Handle loader, TRAPS) NOT_CDS_JAVA_HEAP_RETURN; + + static bool is_lambda_form_klass(InstanceKlass* ik) NOT_CDS_JAVA_HEAP_RETURN_(false); + static bool is_lambda_proxy_klass(InstanceKlass* ik) NOT_CDS_JAVA_HEAP_RETURN_(false); + static bool is_string_concat_klass(InstanceKlass* ik) NOT_CDS_JAVA_HEAP_RETURN_(false); + static bool is_archivable_hidden_klass(InstanceKlass* ik) NOT_CDS_JAVA_HEAP_RETURN_(false); }; #if INCLUDE_CDS_JAVA_HEAP diff --git a/src/hotspot/share/cds/lambdaFormInvokers.cpp b/src/hotspot/share/cds/lambdaFormInvokers.cpp index 271c6c76d7a..5455a5e66f2 100644 --- a/src/hotspot/share/cds/lambdaFormInvokers.cpp +++ b/src/hotspot/share/cds/lambdaFormInvokers.cpp @@ -27,8 +27,8 @@ #include "cds/lambdaFormInvokers.hpp" #include "cds/metaspaceShared.hpp" #include "cds/regeneratedClasses.hpp" -#include "classfile/classLoadInfo.hpp" #include "classfile/classFileStream.hpp" +#include "classfile/classLoadInfo.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/klassFactory.hpp" #include "classfile/symbolTable.hpp" @@ -96,6 +96,21 @@ void LambdaFormInvokers::regenerate_holder_classes(TRAPS) { return; } + if (CDSConfig::is_dumping_static_archive() && CDSConfig::is_dumping_invokedynamic()) { + // Work around JDK-8310831, as some methods in lambda form holder classes may not get generated. + log_info(cds)("Archived MethodHandles may refer to lambda form holder classes. Cannot regenerate."); + return; + } + + if (CDSConfig::is_dumping_dynamic_archive() && CDSConfig::is_dumping_aot_linked_classes() && + CDSConfig::is_using_aot_linked_classes()) { + // The base archive may have some pre-resolved CP entries that point to the lambda form holder + // classes in the base archive. If we generate new versions of these classes, those CP entries + // will be pointing to invalid classes. + log_info(cds)("Base archive already has aot-linked lambda form holder classes. Cannot regenerate."); + return; + } + ResourceMark rm(THREAD); Symbol* cds_name = vmSymbols::jdk_internal_misc_CDS(); diff --git a/src/hotspot/share/cds/metaspaceShared.cpp b/src/hotspot/share/cds/metaspaceShared.cpp index 3cb1374efb1..b2f5f420365 100644 --- a/src/hotspot/share/cds/metaspaceShared.cpp +++ b/src/hotspot/share/cds/metaspaceShared.cpp @@ -23,16 +23,17 @@ */ #include "precompiled.hpp" +#include "cds/aotClassLinker.hpp" +#include "cds/aotConstantPoolResolver.hpp" +#include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveHeapLoader.hpp" #include "cds/archiveHeapWriter.hpp" #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" #include "cds/cdsProtectionDomain.hpp" -#include "cds/cds_globals.hpp" #include "cds/classListParser.hpp" #include "cds/classListWriter.hpp" -#include "cds/classPrelinker.hpp" #include "cds/cppVtables.hpp" #include "cds/dumpAllocStats.hpp" #include "cds/dynamicArchive.hpp" @@ -98,6 +99,7 @@ bool MetaspaceShared::_remapped_readwrite = false; void* MetaspaceShared::_shared_metaspace_static_top = nullptr; intx MetaspaceShared::_relocation_delta; char* MetaspaceShared::_requested_base_address; +Array* MetaspaceShared::_archived_method_handle_intrinsics = nullptr; bool MetaspaceShared::_use_optimized_module_handling = true; // The CDS archive is divided into the following regions: @@ -308,8 +310,12 @@ void MetaspaceShared::post_initialize(TRAPS) { } } +// Extra java.lang.Strings to be added to the archive static GrowableArrayCHeap* _extra_interned_strings = nullptr; +// Extra Symbols to be added to the archive static GrowableArrayCHeap* _extra_symbols = nullptr; +// Methods managed by SystemDictionary::find_method_handle_intrinsic() to be added to the archive +static GrowableArray* _pending_method_handle_intrinsics = NULL; void MetaspaceShared::read_extra_data(JavaThread* current, const char* filename) { _extra_interned_strings = new GrowableArrayCHeap(10000); @@ -360,6 +366,30 @@ void MetaspaceShared::read_extra_data(JavaThread* current, const char* filename) } } +void MetaspaceShared::make_method_handle_intrinsics_shareable() { + for (int i = 0; i < _pending_method_handle_intrinsics->length(); i++) { + Method* m = ArchiveBuilder::current()->get_buffered_addr(_pending_method_handle_intrinsics->at(i)); + m->remove_unshareable_info(); + // Each method has its own constant pool (which is distinct from m->method_holder()->constants()); + m->constants()->remove_unshareable_info(); + } +} + +void MetaspaceShared::write_method_handle_intrinsics() { + int len = _pending_method_handle_intrinsics->length(); + _archived_method_handle_intrinsics = ArchiveBuilder::new_ro_array(len); + int word_size = _archived_method_handle_intrinsics->size(); + for (int i = 0; i < len; i++) { + Method* m = _pending_method_handle_intrinsics->at(i); + ArchiveBuilder::current()->write_pointer_in_buffer(_archived_method_handle_intrinsics->adr_at(i), m); + word_size += m->size() + m->constMethod()->size() + m->constants()->size(); + if (m->constants()->cache() != nullptr) { + word_size += m->constants()->cache()->size(); + } + } + log_info(cds)("Archived %d method handle intrinsics (%d bytes)", len, word_size * BytesPerWord); +} + // About "serialize" -- // // This is (probably a badly named) way to read/write a data stream of pointers and @@ -431,7 +461,7 @@ void MetaspaceShared::serialize(SerializeClosure* soc) { StringTable::serialize_shared_table_header(soc); HeapShared::serialize_tables(soc); SystemDictionaryShared::serialize_dictionary_headers(soc); - + AOTLinkedClassBulkLoader::serialize(soc, true); InstanceMirrorKlass::serialize_offsets(soc); // Dump/restore well known classes (pointers) @@ -439,6 +469,7 @@ void MetaspaceShared::serialize(SerializeClosure* soc) { soc->do_tag(--tag); CDS_JAVA_HEAP_ONLY(ClassLoaderDataShared::serialize(soc);) + soc->do_ptr((void**)&_archived_method_handle_intrinsics); LambdaFormInvokers::serialize(soc); soc->do_tag(666); @@ -526,6 +557,10 @@ public: it->push(_extra_symbols->adr_at(i)); } } + + for (int i = 0; i < _pending_method_handle_intrinsics->length(); i++) { + it->push(_pending_method_handle_intrinsics->adr_at(i)); + } } }; @@ -548,6 +583,8 @@ char* VM_PopulateDumpSharedSpace::dump_read_only_tables() { ArchiveBuilder::OtherROAllocMark mark; SystemDictionaryShared::write_to_archive(); + AOTClassLinker::write_to_archive(); + MetaspaceShared::write_method_handle_intrinsics(); // Write lambform lines into archive LambdaFormInvokers::dump_static_archive_invokers(); @@ -566,13 +603,20 @@ void VM_PopulateDumpSharedSpace::doit() { DEBUG_ONLY(SystemDictionaryShared::NoClassLoadingMark nclm); + _pending_method_handle_intrinsics = new (mtClassShared) GrowableArray(256, mtClassShared); + if (CDSConfig::is_dumping_aot_linked_classes()) { + // When dumping AOT-linked classes, some classes may have direct references to a method handle + // intrinsic. The easiest thing is to save all of them into the AOT cache. + SystemDictionary::get_all_method_handle_intrinsics(_pending_method_handle_intrinsics); + } + FileMapInfo::check_nonempty_dir_in_shared_path_table(); NOT_PRODUCT(SystemDictionary::verify();) // Block concurrent class unloading from changing the _dumptime_table MutexLocker ml(DumpTimeTable_lock, Mutex::_no_safepoint_check_flag); - SystemDictionaryShared::check_excluded_classes(); + SystemDictionaryShared::find_all_archivable_classes(); _builder.gather_source_objs(); _builder.reserve_buffer(); @@ -589,6 +633,7 @@ void VM_PopulateDumpSharedSpace::doit() { log_info(cds)("Make classes shareable"); _builder.make_klasses_shareable(); + MetaspaceShared::make_method_handle_intrinsics_shareable(); char* early_serialized_data = dump_early_read_only_tables(); char* serialized_data = dump_read_only_tables(); @@ -656,12 +701,12 @@ bool MetaspaceShared::link_class_for_cds(InstanceKlass* ik, TRAPS) { // cpcache to be created. Class verification is done according // to -Xverify setting. bool res = MetaspaceShared::try_link_class(THREAD, ik); - ClassPrelinker::dumptime_resolve_constants(ik, CHECK_(false)); + AOTConstantPoolResolver::dumptime_resolve_constants(ik, CHECK_(false)); return res; } void MetaspaceShared::link_shared_classes(bool jcmd_request, TRAPS) { - ClassPrelinker::initialize(); + AOTClassLinker::initialize(); if (!jcmd_request) { LambdaFormInvokers::regenerate_holder_classes(CHECK); @@ -709,6 +754,7 @@ void MetaspaceShared::prepare_for_dumping() { // Preload classes from a list, populate the shared spaces and dump to a // file. void MetaspaceShared::preload_and_dump(TRAPS) { + CDSConfig::DumperThreadMark dumper_thread_mark(THREAD); ResourceMark rm(THREAD); StaticArchiveBuilder builder; preload_and_dump_impl(builder, THREAD); @@ -723,6 +769,15 @@ void MetaspaceShared::preload_and_dump(TRAPS) { MetaspaceShared::writing_error("Unexpected exception, use -Xlog:cds,exceptions=trace for detail"); } } + + if (!CDSConfig::old_cds_flags_used()) { + // The JLI launcher only recognizes the "old" -Xshare:dump flag. + // When the new -XX:AOTMode=create flag is used, we can't return + // to the JLI launcher, as the launcher will fail when trying to + // run the main class, which is not what we want. + tty->print_cr("AOTCache creation is complete: %s", AOTCache); + vm_exit(0); + } } #if INCLUDE_CDS_JAVA_HEAP && defined(_LP64) @@ -851,6 +906,29 @@ void MetaspaceShared::preload_and_dump_impl(StaticArchiveBuilder& builder, TRAPS HeapShared::reset_archived_object_states(CHECK); } + if (CDSConfig::is_dumping_invokedynamic()) { + // This assert means that the MethodType and MethodTypeForm tables won't be + // updated concurrently when we are saving their contents into a side table. + assert(CDSConfig::allow_only_single_java_thread(), "Required"); + + JavaValue result(T_VOID); + JavaCalls::call_static(&result, vmClasses::MethodType_klass(), + vmSymbols::createArchivedObjects(), + vmSymbols::void_method_signature(), + CHECK); + + // java.lang.Class::reflectionFactory cannot be archived yet. We set this field + // to null, and it will be initialized again at runtime. + log_debug(cds)("Resetting Class::reflectionFactory"); + TempNewSymbol method_name = SymbolTable::new_symbol("resetArchivedStates"); + Symbol* method_sig = vmSymbols::void_method_signature(); + JavaCalls::call_static(&result, vmClasses::Class_klass(), + method_name, method_sig, CHECK); + + // Perhaps there is a way to avoid hard-coding these names here. + // See discussion in JDK-8342481. + } + // Do this at the very end, when no Java code will be executed. Otherwise // some new strings may be added to the intern table. StringTable::allocate_shared_strings_array(CHECK); @@ -1536,6 +1614,11 @@ MapArchiveResult MetaspaceShared::map_archive(FileMapInfo* mapinfo, char* mapped early_serialize(&rc); } + if (!mapinfo->validate_aot_class_linking()) { + unmap_archive(mapinfo); + return MAP_ARCHIVE_OTHER_FAILURE; + } + mapinfo->set_is_mapped(true); return MAP_ARCHIVE_SUCCESS; } @@ -1596,6 +1679,18 @@ void MetaspaceShared::initialize_shared_spaces() { dynamic_mapinfo->unmap_region(MetaspaceShared::bm); } + LogStreamHandle(Info, cds) lsh; + if (lsh.is_enabled()) { + lsh.print("Using AOT-linked classes: %s (static archive: %s aot-linked classes", + BOOL_TO_STR(CDSConfig::is_using_aot_linked_classes()), + static_mapinfo->header()->has_aot_linked_classes() ? "has" : "no"); + if (dynamic_mapinfo != nullptr) { + lsh.print(", dynamic archive: %s aot-linked classes", + dynamic_mapinfo->header()->has_aot_linked_classes() ? "has" : "no"); + } + lsh.print_cr(")"); + } + // Set up LambdaFormInvokers::_lambdaform_lines for dynamic dump if (CDSConfig::is_dumping_dynamic_archive()) { // Read stored LF format lines stored in static archive diff --git a/src/hotspot/share/cds/metaspaceShared.hpp b/src/hotspot/share/cds/metaspaceShared.hpp index 0b4f6f2ecd4..aaa649d3c0f 100644 --- a/src/hotspot/share/cds/metaspaceShared.hpp +++ b/src/hotspot/share/cds/metaspaceShared.hpp @@ -34,10 +34,12 @@ class ArchiveBuilder; class ArchiveHeapInfo; class FileMapInfo; +class Method; class outputStream; class SerializeClosure; class StaticArchiveBuilder; +template class Array; template class GrowableArray; enum MapArchiveResult { @@ -56,6 +58,7 @@ class MetaspaceShared : AllStatic { static intx _relocation_delta; static char* _requested_base_address; static bool _use_optimized_module_handling; + static Array* _archived_method_handle_intrinsics; public: enum { @@ -111,6 +114,9 @@ public: static void unrecoverable_writing_error(const char* message = nullptr); static void writing_error(const char* message = nullptr); + static void make_method_handle_intrinsics_shareable() NOT_CDS_RETURN; + static void write_method_handle_intrinsics() NOT_CDS_RETURN; + static Array* archived_method_handle_intrinsics() { return _archived_method_handle_intrinsics; } static void early_serialize(SerializeClosure* sc) NOT_CDS_RETURN; static void serialize(SerializeClosure* sc) NOT_CDS_RETURN; diff --git a/src/hotspot/share/cds/runTimeClassInfo.cpp b/src/hotspot/share/cds/runTimeClassInfo.cpp index e1329d9d2f9..5ad9c14d13e 100644 --- a/src/hotspot/share/cds/runTimeClassInfo.cpp +++ b/src/hotspot/share/cds/runTimeClassInfo.cpp @@ -62,7 +62,7 @@ void RunTimeClassInfo::init(DumpTimeClassInfo& info) { } } - if (k->is_hidden()) { + if (k->is_hidden() && info.nest_host() != nullptr) { _nest_host_offset = builder->any_to_offset_u4(info.nest_host()); } if (k->has_archived_enum_objs()) { diff --git a/src/hotspot/share/cds/runTimeClassInfo.hpp b/src/hotspot/share/cds/runTimeClassInfo.hpp index d0f02d1c095..e3c1ad4f8fe 100644 --- a/src/hotspot/share/cds/runTimeClassInfo.hpp +++ b/src/hotspot/share/cds/runTimeClassInfo.hpp @@ -177,7 +177,11 @@ public: InstanceKlass* nest_host() { assert(!ArchiveBuilder::is_active(), "not called when dumping archive"); - return ArchiveUtils::from_offset(_nest_host_offset); + if (_nest_host_offset == 0) { + return nullptr; + } else { + return ArchiveUtils::from_offset(_nest_host_offset); + } } RTLoaderConstraint* loader_constraints() { diff --git a/src/hotspot/share/classfile/classLoader.cpp b/src/hotspot/share/classfile/classLoader.cpp index 9cf88e7cf55..1b34f2ebede 100644 --- a/src/hotspot/share/classfile/classLoader.cpp +++ b/src/hotspot/share/classfile/classLoader.cpp @@ -26,16 +26,17 @@ #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" #include "cds/filemap.hpp" +#include "cds/heapShared.hpp" #include "classfile/classFileStream.hpp" #include "classfile/classLoader.inline.hpp" #include "classfile/classLoaderData.inline.hpp" #include "classfile/classLoaderExt.hpp" #include "classfile/classLoadInfo.hpp" #include "classfile/javaClasses.hpp" +#include "classfile/klassFactory.hpp" #include "classfile/moduleEntry.hpp" #include "classfile/modules.hpp" #include "classfile/packageEntry.hpp" -#include "classfile/klassFactory.hpp" #include "classfile/symbolTable.hpp" #include "classfile/systemDictionary.hpp" #include "classfile/systemDictionaryShared.hpp" @@ -1273,7 +1274,7 @@ void ClassLoader::record_result(JavaThread* current, InstanceKlass* ik, assert(stream != nullptr, "sanity"); if (ik->is_hidden()) { - // We do not archive hidden classes. + record_hidden_class(ik); return; } @@ -1375,6 +1376,44 @@ void ClassLoader::record_result(JavaThread* current, InstanceKlass* ik, assert(file_name != nullptr, "invariant"); ClassLoaderExt::record_result(checked_cast(classpath_index), ik, redefined); } + +void ClassLoader::record_hidden_class(InstanceKlass* ik) { + assert(ik->is_hidden(), "must be"); + + s2 classloader_type; + if (HeapShared::is_lambda_form_klass(ik)) { + classloader_type = ClassLoader::BOOT_LOADER; + } else { + oop loader = ik->class_loader(); + + if (loader == nullptr) { + classloader_type = ClassLoader::BOOT_LOADER; + } else if (SystemDictionary::is_platform_class_loader(loader)) { + classloader_type = ClassLoader::PLATFORM_LOADER; + } else if (SystemDictionary::is_system_class_loader(loader)) { + classloader_type = ClassLoader::APP_LOADER; + } else { + // This class won't be archived, so no need to update its + // classloader_type/classpath_index. + return; + } + } + ik->set_shared_class_loader_type(classloader_type); + + if (HeapShared::is_lambda_proxy_klass(ik)) { + InstanceKlass* nest_host = ik->nest_host_not_null(); + ik->set_shared_classpath_index(nest_host->shared_classpath_index()); + } else if (HeapShared::is_lambda_form_klass(ik)) { + ik->set_shared_classpath_index(0); + } else { + // Generated invoker classes. + if (classloader_type == ClassLoader::APP_LOADER) { + ik->set_shared_classpath_index(ClassLoaderExt::app_class_paths_start_index()); + } else { + ik->set_shared_classpath_index(0); + } + } +} #endif // INCLUDE_CDS // Initialize the class loader's access to methods in libzip. Parse and diff --git a/src/hotspot/share/classfile/classLoader.hpp b/src/hotspot/share/classfile/classLoader.hpp index e44059b7247..d6780054904 100644 --- a/src/hotspot/share/classfile/classLoader.hpp +++ b/src/hotspot/share/classfile/classLoader.hpp @@ -140,6 +140,7 @@ public: class ClassLoader: AllStatic { public: enum ClassLoaderType { + OTHER = 0, BOOT_LOADER = 1, /* boot loader */ PLATFORM_LOADER = 2, /* PlatformClassLoader */ APP_LOADER = 3 /* AppClassLoader */ @@ -385,6 +386,7 @@ class ClassLoader: AllStatic { static char* uri_to_path(const char* uri); static void record_result(JavaThread* current, InstanceKlass* ik, const ClassFileStream* stream, bool redefined); + static void record_hidden_class(InstanceKlass* ik); #endif static char* lookup_vm_options(); diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index e17de0f2264..12d3ef79cd0 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -867,6 +867,7 @@ int java_lang_Class::_name_offset; int java_lang_Class::_source_file_offset; int java_lang_Class::_classData_offset; int java_lang_Class::_classRedefinedCount_offset; +int java_lang_Class::_reflectionData_offset; bool java_lang_Class::_offsets_computed = false; GrowableArray* java_lang_Class::_fixup_mirror_list = nullptr; @@ -1303,6 +1304,11 @@ void java_lang_Class::set_class_data(oop java_class, oop class_data) { java_class->obj_field_put(_classData_offset, class_data); } +void java_lang_Class::set_reflection_data(oop java_class, oop reflection_data) { + assert(_reflectionData_offset != 0, "must be set"); + java_class->obj_field_put(_reflectionData_offset, reflection_data); +} + void java_lang_Class::set_class_loader(oop java_class, oop loader) { assert(_class_loader_offset != 0, "offsets should have been initialized"); java_class->obj_field_put(_class_loader_offset, loader); @@ -1493,6 +1499,7 @@ oop java_lang_Class::primitive_mirror(BasicType t) { macro(_module_offset, k, "module", module_signature, false); \ macro(_name_offset, k, "name", string_signature, false); \ macro(_classData_offset, k, "classData", object_signature, false); \ + macro(_reflectionData_offset, k, "reflectionData", java_lang_ref_SoftReference_signature, false); \ macro(_signers_offset, k, "signers", object_array_signature, false); void java_lang_Class::compute_offsets() { @@ -5479,20 +5486,18 @@ void JavaClasses::serialize_offsets(SerializeClosure* soc) { bool JavaClasses::is_supported_for_archiving(oop obj) { Klass* klass = obj->klass(); - if (klass == vmClasses::ClassLoader_klass() || // ClassLoader::loader_data is malloc'ed. - // The next 3 classes are used to implement java.lang.invoke, and are not used directly in - // regular Java code. The implementation of java.lang.invoke uses generated hidden classes - // (e.g., as referenced by ResolvedMethodName::vmholder) that are not yet supported by CDS. - // So for now we cannot not support these classes for archiving. - // - // These objects typically are not referenced by static fields, but rather by resolved - // constant pool entries, so excluding them shouldn't affect the archiving of static fields. - klass == vmClasses::ResolvedMethodName_klass() || - klass == vmClasses::MemberName_klass() || - klass == vmClasses::Context_klass() || - // It's problematic to archive Reference objects. One of the reasons is that - // Reference::discovered may pull in unwanted objects (see JDK-8284336) - klass->is_subclass_of(vmClasses::Reference_klass())) { + if (!CDSConfig::is_dumping_invokedynamic()) { + // These are supported by CDS only when CDSConfig::is_dumping_invokedynamic() is enabled. + if (klass == vmClasses::ResolvedMethodName_klass() || + klass == vmClasses::MemberName_klass() || + klass == vmClasses::Context_klass()) { + return false; + } + } + + if (klass->is_subclass_of(vmClasses::Reference_klass())) { + // It's problematic to archive Reference objects. One of the reasons is that + // Reference::discovered may pull in unwanted objects (see JDK-8284336) return false; } diff --git a/src/hotspot/share/classfile/javaClasses.hpp b/src/hotspot/share/classfile/javaClasses.hpp index a2e6ad7349c..dcb94878aed 100644 --- a/src/hotspot/share/classfile/javaClasses.hpp +++ b/src/hotspot/share/classfile/javaClasses.hpp @@ -254,6 +254,7 @@ class java_lang_Class : AllStatic { static int _source_file_offset; static int _classData_offset; static int _classRedefinedCount_offset; + static int _reflectionData_offset; static bool _offsets_computed; @@ -321,6 +322,7 @@ class java_lang_Class : AllStatic { static objArrayOop signers(oop java_class); static oop class_data(oop java_class); static void set_class_data(oop java_class, oop classData); + static void set_reflection_data(oop java_class, oop reflection_data); static int component_mirror_offset() { return _component_mirror_offset; } diff --git a/src/hotspot/share/classfile/systemDictionary.cpp b/src/hotspot/share/classfile/systemDictionary.cpp index 2d0ebfb7266..b9b341c4dab 100644 --- a/src/hotspot/share/classfile/systemDictionary.cpp +++ b/src/hotspot/share/classfile/systemDictionary.cpp @@ -82,6 +82,7 @@ #include "services/diagnosticCommand.hpp" #include "services/finalizerService.hpp" #include "services/threadService.hpp" +#include "utilities/growableArray.hpp" #include "utilities/macros.hpp" #include "utilities/utf8.hpp" #if INCLUDE_CDS @@ -134,19 +135,6 @@ oop SystemDictionary::java_platform_loader() { } void SystemDictionary::compute_java_loaders(TRAPS) { - if (_java_system_loader.is_empty()) { - oop system_loader = get_system_class_loader_impl(CHECK); - _java_system_loader = OopHandle(Universe::vm_global(), system_loader); - } else { - // It must have been restored from the archived module graph - assert(CDSConfig::is_using_archive(), "must be"); - assert(CDSConfig::is_using_full_module_graph(), "must be"); - DEBUG_ONLY( - oop system_loader = get_system_class_loader_impl(CHECK); - assert(_java_system_loader.resolve() == system_loader, "must be"); - ) - } - if (_java_platform_loader.is_empty()) { oop platform_loader = get_platform_class_loader_impl(CHECK); _java_platform_loader = OopHandle(Universe::vm_global(), platform_loader); @@ -158,6 +146,19 @@ void SystemDictionary::compute_java_loaders(TRAPS) { oop platform_loader = get_platform_class_loader_impl(CHECK); assert(_java_platform_loader.resolve() == platform_loader, "must be"); ) + } + + if (_java_system_loader.is_empty()) { + oop system_loader = get_system_class_loader_impl(CHECK); + _java_system_loader = OopHandle(Universe::vm_global(), system_loader); + } else { + // It must have been restored from the archived module graph + assert(CDSConfig::is_using_archive(), "must be"); + assert(CDSConfig::is_using_full_module_graph(), "must be"); + DEBUG_ONLY( + oop system_loader = get_system_class_loader_impl(CHECK); + assert(_java_system_loader.resolve() == system_loader, "must be"); + ) } } @@ -1718,6 +1719,23 @@ void SystemDictionary::update_dictionary(JavaThread* current, mu1.notify_all(); } +#if INCLUDE_CDS +// Indicate that loader_data has initiated the loading of class k, which +// has already been defined by a parent loader. +// This API should be used only by AOTLinkedClassBulkLoader +void SystemDictionary::add_to_initiating_loader(JavaThread* current, + InstanceKlass* k, + ClassLoaderData* loader_data) { + assert(CDSConfig::is_using_aot_linked_classes(), "must be"); + assert_locked_or_safepoint(SystemDictionary_lock); + Symbol* name = k->name(); + Dictionary* dictionary = loader_data->dictionary(); + assert(k->is_loaded(), "must be"); + assert(k->class_loader_data() != loader_data, "only for classes defined by a parent loader"); + assert(dictionary->find_class(current, name) == nullptr, "sanity"); + dictionary->add_klass(current, name, k); +} +#endif // Try to find a class name using the loader constraints. The // loader constraints might know about a class that isn't fully loaded @@ -2033,6 +2051,52 @@ Method* SystemDictionary::find_method_handle_intrinsic(vmIntrinsicID iid, return nullptr; } +#if INCLUDE_CDS +void SystemDictionary::get_all_method_handle_intrinsics(GrowableArray* methods) { + assert(SafepointSynchronize::is_at_safepoint(), "must be"); + auto do_method = [&] (InvokeMethodKey& key, Method*& m) { + methods->append(m); + }; + _invoke_method_intrinsic_table->iterate_all(do_method); +} + +void SystemDictionary::restore_archived_method_handle_intrinsics() { + if (UseSharedSpaces) { + EXCEPTION_MARK; + restore_archived_method_handle_intrinsics_impl(THREAD); + if (HAS_PENDING_EXCEPTION) { + // This is probably caused by OOM -- other parts of the CDS archive have direct pointers to + // the archived method handle intrinsics, so we can't really recover from this failure. + vm_exit_during_initialization(err_msg("Failed to restore archived method handle intrinsics. Try to increase heap size.")); + } + } +} + +void SystemDictionary::restore_archived_method_handle_intrinsics_impl(TRAPS) { + Array* list = MetaspaceShared::archived_method_handle_intrinsics(); + for (int i = 0; i < list->length(); i++) { + methodHandle m(THREAD, list->at(i)); + Method::restore_archived_method_handle_intrinsic(m, CHECK); + m->constants()->restore_unshareable_info(CHECK); + if (!Arguments::is_interpreter_only() || m->intrinsic_id() == vmIntrinsics::_linkToNative) { + AdapterHandlerLibrary::create_native_wrapper(m); + if (!m->has_compiled_code()) { + ResourceMark rm(THREAD); + vm_exit_during_initialization(err_msg("Failed to initialize method %s", m->external_name())); + } + } + + // There's no need to grab the InvokeMethodIntrinsicTable_lock, as we are still very early in + // VM start-up -- in init_globals2() -- so we are still running a single Java thread. It's not + // possible to have a contention. + const int iid_as_int = vmIntrinsics::as_int(m->intrinsic_id()); + InvokeMethodKey key(m->signature(), iid_as_int); + bool created = _invoke_method_intrinsic_table->put(key, m()); + assert(created, "unexpected contention"); + } +} +#endif // INCLUDE_CDS + // Helper for unpacking the return value from linkMethod and linkCallSite. static Method* unpack_method_and_appendix(Handle mname, Klass* accessing_klass, diff --git a/src/hotspot/share/classfile/systemDictionary.hpp b/src/hotspot/share/classfile/systemDictionary.hpp index 04980291716..9f5f34ee773 100644 --- a/src/hotspot/share/classfile/systemDictionary.hpp +++ b/src/hotspot/share/classfile/systemDictionary.hpp @@ -75,7 +75,10 @@ class GCTimer; class EventClassLoad; class Symbol; +template class GrowableArray; + class SystemDictionary : AllStatic { + friend class AOTLinkedClassBulkLoader; friend class BootstrapInfo; friend class vmClasses; friend class VMStructs; @@ -239,6 +242,9 @@ public: Symbol* signature, TRAPS); + static void get_all_method_handle_intrinsics(GrowableArray* methods) NOT_CDS_RETURN; + static void restore_archived_method_handle_intrinsics() NOT_CDS_RETURN; + // compute java_mirror (java.lang.Class instance) for a type ("I", "[[B", "LFoo;", etc.) // Either the accessing_klass or the CL/PD can be non-null, but not both. static Handle find_java_mirror_for_type(Symbol* signature, @@ -293,6 +299,9 @@ public: const char* message); static const char* find_nest_host_error(const constantPoolHandle& pool, int which); + static void add_to_initiating_loader(JavaThread* current, InstanceKlass* k, + ClassLoaderData* loader_data) NOT_CDS_RETURN; + static OopHandle _java_system_loader; static OopHandle _java_platform_loader; @@ -331,6 +340,7 @@ private: Handle protection_domain, TRAPS); // Second part of load_shared_class static void load_shared_class_misc(InstanceKlass* ik, ClassLoaderData* loader_data) NOT_CDS_RETURN; + static void restore_archived_method_handle_intrinsics_impl(TRAPS) NOT_CDS_RETURN; protected: // Used by SystemDictionaryShared diff --git a/src/hotspot/share/classfile/systemDictionaryShared.cpp b/src/hotspot/share/classfile/systemDictionaryShared.cpp index 08f224d969f..84b7ea0d6b9 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.cpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.cpp @@ -27,12 +27,13 @@ #include "cds/archiveHeapLoader.hpp" #include "cds/archiveUtils.hpp" #include "cds/cdsConfig.hpp" +#include "cds/cdsProtectionDomain.hpp" #include "cds/classListParser.hpp" #include "cds/classListWriter.hpp" +#include "cds/dumpTimeClassInfo.inline.hpp" #include "cds/dynamicArchive.hpp" #include "cds/filemap.hpp" -#include "cds/cdsProtectionDomain.hpp" -#include "cds/dumpTimeClassInfo.inline.hpp" +#include "cds/heapShared.hpp" #include "cds/metaspaceShared.hpp" #include "cds/runTimeClassInfo.hpp" #include "classfile/classFileStream.hpp" @@ -204,6 +205,15 @@ DumpTimeClassInfo* SystemDictionaryShared::get_info_locked(InstanceKlass* k) { return info; } +void SystemDictionaryShared::mark_required_hidden_class(InstanceKlass* k) { + assert(k->is_hidden(), "sanity"); + DumpTimeClassInfo* info = _dumptime_table->get(k); + ResourceMark rm; + if (info != nullptr) { + info->set_is_required_hidden_class(); + } +} + bool SystemDictionaryShared::check_for_exclusion(InstanceKlass* k, DumpTimeClassInfo* info) { if (MetaspaceShared::is_in_shared_metaspace(k)) { // We have reached a super type that's already in the base archive. Treat it @@ -287,9 +297,13 @@ bool SystemDictionaryShared::check_for_exclusion_impl(InstanceKlass* k) { if (!k->is_hidden() && k->shared_classpath_index() < 0 && is_builtin(k)) { if (k->name()->starts_with("java/lang/invoke/BoundMethodHandle$Species_")) { // This class is dynamically generated by the JDK - ResourceMark rm; - log_info(cds)("Skipping %s because it is dynamically generated", k->name()->as_C_string()); - return true; // exclude without warning + if (CDSConfig::is_dumping_aot_linked_classes()) { + k->set_shared_classpath_index(0); + } else { + ResourceMark rm; + log_info(cds)("Skipping %s because it is dynamically generated", k->name()->as_C_string()); + return true; // exclude without warning + } } else { // These are classes loaded from unsupported locations (such as those loaded by JVMTI native // agent during dump time). @@ -326,9 +340,8 @@ bool SystemDictionaryShared::check_for_exclusion_impl(InstanceKlass* k) { } } - if (k->is_hidden() && !is_registered_lambda_proxy_class(k)) { - ResourceMark rm; - log_debug(cds)("Skipping %s: Hidden class", k->name()->as_C_string()); + if (k->is_hidden() && !should_hidden_class_be_archived(k)) { + log_info(cds)("Skipping %s: Hidden class", k->name()->as_C_string()); return true; } @@ -588,7 +601,11 @@ void SystemDictionaryShared::validate_before_archiving(InstanceKlass* k) { guarantee(!info->is_excluded(), "Should not attempt to archive excluded class %s", name); if (is_builtin(k)) { if (k->is_hidden()) { - assert(is_registered_lambda_proxy_class(k), "unexpected hidden class %s", name); + if (CDSConfig::is_dumping_invokedynamic()) { + assert(should_hidden_class_be_archived(k), "unexpected hidden class %s", name); + } else { + assert(is_registered_lambda_proxy_class(k), "unexpected hidden class %s", name); + } } guarantee(!k->is_shared_unregistered_class(), "Class loader type must be set for BUILTIN class %s", name); @@ -640,7 +657,93 @@ public: } }; -void SystemDictionaryShared::check_excluded_classes() { +void SystemDictionaryShared::scan_constant_pool(InstanceKlass* k) { + if (CDSConfig::is_dumping_invokedynamic()) { + k->constants()->find_required_hidden_classes(); + } +} + +bool SystemDictionaryShared::should_hidden_class_be_archived(InstanceKlass* k) { + assert(k->is_hidden(), "sanity"); + if (is_registered_lambda_proxy_class(k)) { + return true; + } + + if (CDSConfig::is_dumping_invokedynamic()) { + DumpTimeClassInfo* info = _dumptime_table->get(k); + if (info != nullptr && info->is_required_hidden_class()) { + guarantee(HeapShared::is_archivable_hidden_klass(k), "required hidden class must be archivable"); + return true; + } + } + + return false; +} + +// Returns true if the class should be excluded. This can be called by +// AOTConstantPoolResolver before or after we enter the CDS safepoint. +// When called before the safepoint, we need to link the class so that +// it can be checked by check_for_exclusion(). +bool SystemDictionaryShared::should_be_excluded(Klass* k) { + assert(CDSConfig::is_dumping_archive(), "sanity"); + assert(CDSConfig::current_thread_is_vm_or_dumper(), "sanity"); + + if (k->is_objArray_klass()) { + return should_be_excluded(ObjArrayKlass::cast(k)->bottom_klass()); + } + + if (!k->is_instance_klass()) { + return false; + } else { + InstanceKlass* ik = InstanceKlass::cast(k); + + if (!SafepointSynchronize::is_at_safepoint()) { + if (!ik->is_linked()) { + // check_for_exclusion() below doesn't link unlinked classes. We come + // here only when we are trying to aot-link constant pool entries, so + // we'd better link the class. + JavaThread* THREAD = JavaThread::current(); + ik->link_class(THREAD); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; + return true; // linking failed -- let's exclude it + } + } + + MutexLocker ml(DumpTimeTable_lock, Mutex::_no_safepoint_check_flag); + DumpTimeClassInfo* p = get_info_locked(ik); + if (p->is_excluded()) { + return true; + } + return check_for_exclusion(ik, p); + } else { + // No need to check for is_linked() as all eligible classes should have + // already been linked in MetaspaceShared::link_class_for_cds(). + // Can't take the lock as we are in safepoint. + DumpTimeClassInfo* p = _dumptime_table->get(ik); + if (p->is_excluded()) { + return true; + } + return check_for_exclusion(ik, p); + } + } +} + +void SystemDictionaryShared::find_all_archivable_classes() { + HeapShared::start_finding_required_hidden_classes(); + find_all_archivable_classes_impl(); + HeapShared::end_finding_required_hidden_classes(); +} + +// Iterate over all the classes in _dumptime_table, marking the ones that must be +// excluded from the archive. Those that are not excluded will be archivable. +// +// (a) Non-hidden classes are easy. They are only check by the rules in +// SystemDictionaryShared::check_for_exclusion(). +// (b) For hidden classes, we only archive those that are required (i.e., they are +// referenced by Java objects (such as CallSites) that are reachable from +// ConstantPools). This needs help from HeapShared. +void SystemDictionaryShared::find_all_archivable_classes_impl() { assert(!class_loading_may_happen(), "class loading must be disabled"); assert_lock_strong(DumpTimeTable_lock); @@ -653,10 +756,56 @@ void SystemDictionaryShared::check_excluded_classes() { dup_checker.mark_duplicated_classes(); } - auto check_for_exclusion = [&] (InstanceKlass* k, DumpTimeClassInfo& info) { - SystemDictionaryShared::check_for_exclusion(k, &info); + ResourceMark rm; + + // First, scan all non-hidden classes + auto check_non_hidden = [&] (InstanceKlass* k, DumpTimeClassInfo& info) { + if (!k->is_hidden()) { + SystemDictionaryShared::check_for_exclusion(k, &info); + if (!info.is_excluded() && !info.has_scanned_constant_pool()) { + scan_constant_pool(k); + info.set_has_scanned_constant_pool(); + } + } }; - _dumptime_table->iterate_all_live_classes(check_for_exclusion); + _dumptime_table->iterate_all_live_classes(check_non_hidden); + + // Then, scan all the hidden classes that have been marked as required to + // discover more hidden classes. Stop when we cannot make progress anymore. + bool made_progress; + do { + made_progress = false; + auto check_hidden = [&] (InstanceKlass* k, DumpTimeClassInfo& info) { + if (k->is_hidden() && should_hidden_class_be_archived(k)) { + SystemDictionaryShared::check_for_exclusion(k, &info); + if (info.is_excluded()) { + guarantee(!info.is_required_hidden_class(), "A required hidden class cannot be marked as excluded"); + } else if (!info.has_scanned_constant_pool()) { + scan_constant_pool(k); + info.set_has_scanned_constant_pool(); + // The CP entries in k *MAY* refer to other hidden classes, so scan + // every hidden class again. + made_progress = true; + } + } + }; + _dumptime_table->iterate_all_live_classes(check_hidden); + } while (made_progress); + + // Now, all hidden classes that have not yet been scanned must be marked as excluded + auto exclude_remaining_hidden = [&] (InstanceKlass* k, DumpTimeClassInfo& info) { + if (k->is_hidden()) { + SystemDictionaryShared::check_for_exclusion(k, &info); + if (CDSConfig::is_dumping_invokedynamic()) { + if (should_hidden_class_be_archived(k)) { + guarantee(!info.is_excluded(), "Must be"); + } else { + guarantee(info.is_excluded(), "Must be"); + } + } + } + }; + _dumptime_table->iterate_all_live_classes(exclude_remaining_hidden); _dumptime_table->update_counts(); cleanup_lambda_proxy_class_dictionary(); @@ -766,6 +915,11 @@ void SystemDictionaryShared::add_lambda_proxy_class(InstanceKlass* caller_ik, Method* member_method, Symbol* instantiated_method_type, TRAPS) { + if (CDSConfig::is_dumping_invokedynamic()) { + // The lambda proxy classes will be stored as part of aot-resolved constant pool entries. + // There's no need to remember them in a separate table. + return; + } assert(caller_ik->class_loader() == lambda_ik->class_loader(), "mismatched class loader"); assert(caller_ik->class_loader_data() == lambda_ik->class_loader_data(), "mismatched class loader data"); @@ -1188,7 +1342,7 @@ public: // In static dump, info._proxy_klasses->at(0) is already relocated to point to the archived class // (not the original class). // - // The following check has been moved to SystemDictionaryShared::check_excluded_classes(), which + // The following check has been moved to SystemDictionaryShared::find_all_archivable_classes(), which // happens before the classes are copied. // // if (SystemDictionaryShared::is_excluded_class(info._proxy_klasses->at(0))) { diff --git a/src/hotspot/share/classfile/systemDictionaryShared.hpp b/src/hotspot/share/classfile/systemDictionaryShared.hpp index c79821036e0..5e0c54a6ffc 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.hpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.hpp @@ -188,6 +188,7 @@ private: static DumpTimeClassInfo* get_info(InstanceKlass* k); static DumpTimeClassInfo* get_info_locked(InstanceKlass* k); + static void find_all_archivable_classes_impl(); static void write_dictionary(RunTimeSharedDictionary* dictionary, bool is_builtin); static void write_lambda_proxy_class_dictionary(LambdaProxyClassDictionary* dictionary); @@ -199,10 +200,12 @@ private: static void remove_dumptime_info(InstanceKlass* k) NOT_CDS_RETURN; static bool has_been_redefined(InstanceKlass* k); static InstanceKlass* retrieve_lambda_proxy_class(const RunTimeLambdaProxyClassInfo* info) NOT_CDS_RETURN_(nullptr); - + static void scan_constant_pool(InstanceKlass* k); DEBUG_ONLY(static bool _class_loading_may_happen;) public: + static bool should_hidden_class_be_archived(InstanceKlass* k); + static void mark_required_hidden_class(InstanceKlass* k); static bool is_hidden_lambda_proxy(InstanceKlass* ik); static bool is_early_klass(InstanceKlass* k); // Was k loaded while JvmtiExport::is_early_phase()==true static bool has_archived_enum_objs(InstanceKlass* ik); @@ -288,7 +291,8 @@ public: } static bool add_unregistered_class(Thread* current, InstanceKlass* k); - static void check_excluded_classes(); + static void find_all_archivable_classes(); + static bool should_be_excluded(Klass* k); static bool check_for_exclusion(InstanceKlass* k, DumpTimeClassInfo* info); static void validate_before_archiving(InstanceKlass* k); static bool is_excluded_class(InstanceKlass* k); diff --git a/src/hotspot/share/classfile/vmClassMacros.hpp b/src/hotspot/share/classfile/vmClassMacros.hpp index 395e718c55d..46d601d17dd 100644 --- a/src/hotspot/share/classfile/vmClassMacros.hpp +++ b/src/hotspot/share/classfile/vmClassMacros.hpp @@ -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 @@ -175,6 +175,7 @@ do_klass(Short_klass, java_lang_Short ) \ do_klass(Integer_klass, java_lang_Integer ) \ do_klass(Long_klass, java_lang_Long ) \ + do_klass(Void_klass, java_lang_Void ) \ \ /* force inline of iterators */ \ do_klass(Iterator_klass, java_util_Iterator ) \ diff --git a/src/hotspot/share/classfile/vmClasses.cpp b/src/hotspot/share/classfile/vmClasses.cpp index b62d699dfe2..553864ef0b9 100644 --- a/src/hotspot/share/classfile/vmClasses.cpp +++ b/src/hotspot/share/classfile/vmClasses.cpp @@ -23,6 +23,7 @@ */ #include "precompiled.hpp" +#include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/archiveHeapLoader.hpp" #include "cds/cdsConfig.hpp" #include "classfile/classLoader.hpp" @@ -210,6 +211,9 @@ void vmClasses::resolve_all(TRAPS) { #endif InstanceStackChunkKlass::init_offset_of_stack(); + if (CDSConfig::is_using_aot_linked_classes()) { + AOTLinkedClassBulkLoader::load_javabase_classes(THREAD); + } } #if INCLUDE_CDS diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 34861b52d54..0728e156f82 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -89,6 +89,7 @@ class SerializeClosure; template(java_lang_Integer_IntegerCache, "java/lang/Integer$IntegerCache") \ template(java_lang_Long, "java/lang/Long") \ template(java_lang_Long_LongCache, "java/lang/Long$LongCache") \ + template(java_lang_Void, "java/lang/Void") \ \ template(jdk_internal_vm_vector_VectorSupport, "jdk/internal/vm/vector/VectorSupport") \ template(jdk_internal_vm_vector_VectorPayload, "jdk/internal/vm/vector/VectorSupport$VectorPayload") \ @@ -309,6 +310,8 @@ class SerializeClosure; template(jdk_internal_vm_annotation_JvmtiHideEvents_signature, "Ljdk/internal/vm/annotation/JvmtiHideEvents;") \ template(jdk_internal_vm_annotation_JvmtiMountTransition_signature, "Ljdk/internal/vm/annotation/JvmtiMountTransition;") \ \ + template(java_lang_ref_SoftReference_signature, "Ljava/lang/ref/SoftReference;") \ + \ /* Support for JSR 292 & invokedynamic (JDK 1.7 and above) */ \ template(java_lang_invoke_CallSite, "java/lang/invoke/CallSite") \ template(java_lang_invoke_ConstantCallSite, "java/lang/invoke/ConstantCallSite") \ @@ -726,6 +729,7 @@ class SerializeClosure; JFR_TEMPLATES(template) \ \ /* CDS */ \ + template(createArchivedObjects, "createArchivedObjects") \ template(dumpSharedArchive, "dumpSharedArchive") \ template(dumpSharedArchive_signature, "(ZLjava/lang/String;)Ljava/lang/String;") \ template(generateLambdaFormHolderClasses, "generateLambdaFormHolderClasses") \ @@ -739,6 +743,7 @@ class SerializeClosure; template(jdk_internal_misc_CDS, "jdk/internal/misc/CDS") \ template(java_util_concurrent_ConcurrentHashMap, "java/util/concurrent/ConcurrentHashMap") \ template(java_util_ArrayList, "java/util/ArrayList") \ + template(runtimeSetup, "runtimeSetup") \ template(toFileURL_name, "toFileURL") \ template(toFileURL_signature, "(Ljava/lang/String;)Ljava/net/URL;") \ template(url_array_classloader_void_signature, "([Ljava/net/URL;Ljava/lang/ClassLoader;)V") \ diff --git a/src/hotspot/share/interpreter/interpreterRuntime.cpp b/src/hotspot/share/interpreter/interpreterRuntime.cpp index 1d8268dbda6..cb85513eed0 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.cpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.cpp @@ -949,6 +949,15 @@ void InterpreterRuntime::resolve_invokehandle(JavaThread* current) { pool->cache()->set_method_handle(method_index, info); } +void InterpreterRuntime::cds_resolve_invokehandle(int raw_index, + constantPoolHandle& pool, TRAPS) { + const Bytecodes::Code bytecode = Bytecodes::_invokehandle; + CallInfo info; + LinkResolver::resolve_invoke(info, Handle(), pool, raw_index, bytecode, CHECK); + + pool->cache()->set_method_handle(raw_index, info); +} + // First time execution: Resolve symbols, create a permanent CallSite object. void InterpreterRuntime::resolve_invokedynamic(JavaThread* current) { LastFrameAccessor last_frame(current); @@ -968,6 +977,14 @@ void InterpreterRuntime::resolve_invokedynamic(JavaThread* current) { pool->cache()->set_dynamic_call(info, index); } +void InterpreterRuntime::cds_resolve_invokedynamic(int raw_index, + constantPoolHandle& pool, TRAPS) { + const Bytecodes::Code bytecode = Bytecodes::_invokedynamic; + CallInfo info; + LinkResolver::resolve_invoke(info, Handle(), pool, raw_index, bytecode, CHECK); + pool->cache()->set_dynamic_call(info, raw_index); +} + // This function is the interface to the assembly code. It returns the resolved // cpCache entry. This doesn't safepoint, but the helper routines safepoint. // This function will check for redefinition! diff --git a/src/hotspot/share/interpreter/interpreterRuntime.hpp b/src/hotspot/share/interpreter/interpreterRuntime.hpp index 61041694fc6..3635433f434 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.hpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.hpp @@ -92,12 +92,15 @@ class InterpreterRuntime: AllStatic { static void resolve_from_cache(JavaThread* current, Bytecodes::Code bytecode); - // Used by ClassPrelinker + // Used by AOTConstantPoolResolver static void resolve_get_put(Bytecodes::Code bytecode, int field_index, methodHandle& m, constantPoolHandle& pool, bool initialize_holder, TRAPS); static void cds_resolve_invoke(Bytecodes::Code bytecode, int method_index, constantPoolHandle& pool, TRAPS); - + static void cds_resolve_invokehandle(int raw_index, + constantPoolHandle& pool, TRAPS); + static void cds_resolve_invokedynamic(int raw_index, + constantPoolHandle& pool, TRAPS); private: // Statics & fields static void resolve_get_put(JavaThread* current, Bytecodes::Code bytecode); diff --git a/src/hotspot/share/logging/logTag.hpp b/src/hotspot/share/logging/logTag.hpp index b53a015b0f2..d9563e41ce7 100644 --- a/src/hotspot/share/logging/logTag.hpp +++ b/src/hotspot/share/logging/logTag.hpp @@ -38,6 +38,7 @@ class outputStream; LOG_TAG(age) \ LOG_TAG(alloc) \ LOG_TAG(annotation) \ + LOG_TAG(aot) \ LOG_TAG(arguments) \ LOG_TAG(array) \ LOG_TAG(attach) \ diff --git a/src/hotspot/share/memory/iterator.inline.hpp b/src/hotspot/share/memory/iterator.inline.hpp index 532782b18f1..7ed2b9b3faa 100644 --- a/src/hotspot/share/memory/iterator.inline.hpp +++ b/src/hotspot/share/memory/iterator.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -27,6 +27,7 @@ #include "memory/iterator.hpp" +#include "cds/aotLinkedClassBulkLoader.hpp" #include "classfile/classLoaderData.hpp" #include "code/nmethod.hpp" #include "oops/access.inline.hpp" @@ -51,7 +52,11 @@ inline void ClaimMetadataVisitingOopIterateClosure::do_cld(ClassLoaderData* cld) inline void ClaimMetadataVisitingOopIterateClosure::do_klass(Klass* k) { ClassLoaderData* cld = k->class_loader_data(); - ClaimMetadataVisitingOopIterateClosure::do_cld(cld); + if (cld != nullptr) { + ClaimMetadataVisitingOopIterateClosure::do_cld(cld); + } else { + assert(AOTLinkedClassBulkLoader::is_pending_aot_linked_class(k), "sanity"); + } } inline void ClaimMetadataVisitingOopIterateClosure::do_nmethod(nmethod* nm) { diff --git a/src/hotspot/share/oops/constantPool.cpp b/src/hotspot/share/oops/constantPool.cpp index 7147e2588c9..a435d6e5fd3 100644 --- a/src/hotspot/share/oops/constantPool.cpp +++ b/src/hotspot/share/oops/constantPool.cpp @@ -23,11 +23,11 @@ */ #include "precompiled.hpp" -#include "cds/archiveHeapWriter.hpp" -#include "cds/archiveHeapLoader.hpp" +#include "cds/aotConstantPoolResolver.hpp" #include "cds/archiveBuilder.hpp" +#include "cds/archiveHeapLoader.hpp" +#include "cds/archiveHeapWriter.hpp" #include "cds/cdsConfig.hpp" -#include "cds/classPrelinker.hpp" #include "cds/heapShared.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.hpp" @@ -35,6 +35,7 @@ #include "classfile/metadataOnStackMark.hpp" #include "classfile/stringTable.hpp" #include "classfile/systemDictionary.hpp" +#include "classfile/systemDictionaryShared.hpp" #include "classfile/vmClasses.hpp" #include "classfile/vmSymbols.hpp" #include "code/codeCache.hpp" @@ -283,6 +284,44 @@ void ConstantPool::klass_at_put(int class_index, Klass* k) { } #if INCLUDE_CDS_JAVA_HEAP +template +void ConstantPool::iterate_archivable_resolved_references(Function function) { + objArrayOop rr = resolved_references(); + if (rr != nullptr && cache() != nullptr && CDSConfig::is_dumping_invokedynamic()) { + Array* indy_entries = cache()->resolved_indy_entries(); + if (indy_entries != nullptr) { + for (int i = 0; i < indy_entries->length(); i++) { + ResolvedIndyEntry *rie = indy_entries->adr_at(i); + if (rie->is_resolved() && AOTConstantPoolResolver::is_resolution_deterministic(this, rie->constant_pool_index())) { + int rr_index = rie->resolved_references_index(); + assert(resolved_reference_at(rr_index) != nullptr, "must exist"); + function(rr_index); + + // Save the BSM as well (sometimes the JIT looks up the BSM it for replay) + int indy_cp_index = rie->constant_pool_index(); + int bsm_mh_cp_index = bootstrap_method_ref_index_at(indy_cp_index); + int bsm_rr_index = cp_to_object_index(bsm_mh_cp_index); + assert(resolved_reference_at(bsm_rr_index) != nullptr, "must exist"); + function(bsm_rr_index); + } + } + } + + Array* method_entries = cache()->resolved_method_entries(); + if (method_entries != nullptr) { + for (int i = 0; i < method_entries->length(); i++) { + ResolvedMethodEntry* rme = method_entries->adr_at(i); + if (rme->is_resolved(Bytecodes::_invokehandle) && rme->has_appendix() && + cache()->can_archive_resolved_method(this, rme)) { + int rr_index = rme->resolved_references_index(); + assert(resolved_reference_at(rr_index) != nullptr, "must exist"); + function(rr_index); + } + } + } + } +} + // Returns the _resolved_reference array after removing unarchivable items from it. // Returns null if this class is not supported, or _resolved_reference doesn't exist. objArrayOop ConstantPool::prepare_resolved_references_for_archiving() { @@ -300,8 +339,15 @@ objArrayOop ConstantPool::prepare_resolved_references_for_archiving() { objArrayOop rr = resolved_references(); if (rr != nullptr) { + ResourceMark rm; int rr_len = rr->length(); + GrowableArray keep_resolved_refs(rr_len, rr_len, false); + ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(this); + src_cp->iterate_archivable_resolved_references([&](int rr_index) { + keep_resolved_refs.at_put(rr_index, true); + }); + objArrayOop scratch_rr = HeapShared::scratch_resolved_references(src_cp); Array* ref_map = reference_map(); int ref_map_len = ref_map == nullptr ? 0 : ref_map->length(); @@ -316,8 +362,13 @@ objArrayOop ConstantPool::prepare_resolved_references_for_archiving() { if (!ArchiveHeapWriter::is_string_too_large_to_archive(obj)) { scratch_rr->obj_at_put(i, obj); } + continue; } } + + if (keep_resolved_refs.at(i)) { + scratch_rr->obj_at_put(i, obj); + } } } return scratch_rr; @@ -325,6 +376,32 @@ objArrayOop ConstantPool::prepare_resolved_references_for_archiving() { return rr; } +void ConstantPool::find_required_hidden_classes() { + if (_cache == nullptr) { + return; + } + + ClassLoaderData* loader_data = pool_holder()->class_loader_data(); + if (loader_data == nullptr) { + // These are custom loader classes from the preimage + return; + } + + if (!SystemDictionaryShared::is_builtin_loader(loader_data)) { + // Archiving resolved references for classes from non-builtin loaders + // is not yet supported. + return; + } + + objArrayOop rr = resolved_references(); + if (rr != nullptr) { + iterate_archivable_resolved_references([&](int rr_index) { + oop obj = rr->obj_at(rr_index); + HeapShared::find_required_hidden_classes_in_object(obj); + }); + } +} + void ConstantPool::add_dumped_interned_strings() { objArrayOop rr = resolved_references(); if (rr != nullptr) { @@ -349,6 +426,11 @@ void ConstantPool::restore_unshareable_info(TRAPS) { assert(is_constantPool(), "ensure C++ vtable is restored"); assert(on_stack(), "should always be set for shared constant pools"); assert(is_shared(), "should always be set for shared constant pools"); + if (is_for_method_handle_intrinsic()) { + // See the same check in remove_unshareable_info() below. + assert(cache() == NULL, "must not have cpCache"); + return; + } assert(_cache != nullptr, "constant pool _cache should not be null"); // Only create the new resolved references array if it hasn't been attempted before @@ -388,6 +470,14 @@ 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 (is_for_method_handle_intrinsic()) { + // This CP was created by Method::make_method_handle_intrinsic() and has nothing + // that need to be removed/restored. It has no cpCache since the intrinsic methods + // don't have any bytecodes. + assert(cache() == NULL, "must not have cpCache"); + 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) { @@ -482,7 +572,7 @@ void ConstantPool::remove_resolved_klass_if_non_deterministic(int cp_index) { can_archive = false; } else { ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(this); - can_archive = ClassPrelinker::is_resolution_deterministic(src_cp, cp_index); + can_archive = AOTConstantPoolResolver::is_resolution_deterministic(src_cp, cp_index); } if (!can_archive) { @@ -502,7 +592,7 @@ void ConstantPool::remove_resolved_klass_if_non_deterministic(int cp_index) { (!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()); + log.print(" => %s", name->as_C_string()); } } @@ -748,9 +838,7 @@ int ConstantPool::to_cp_index(int index, Bytecodes::Code code) { case Bytecodes::_fast_invokevfinal: // Bytecode interpreter uses this return resolved_method_entry_at(index)->constant_pool_index(); default: - tty->print_cr("Unexpected bytecode: %d", code); - ShouldNotReachHere(); // All cases should have been handled - return -1; + fatal("Unexpected bytecode: %s", Bytecodes::name(code)); } } diff --git a/src/hotspot/share/oops/constantPool.hpp b/src/hotspot/share/oops/constantPool.hpp index bcc9a08dd6c..9ada3e29d49 100644 --- a/src/hotspot/share/oops/constantPool.hpp +++ b/src/hotspot/share/oops/constantPool.hpp @@ -82,7 +82,7 @@ class ConstantPool : public Metadata { friend class JVMCIVMStructs; friend class BytecodeInterpreter; // Directly extracts a klass in the pool for fast instanceof/checkcast friend class Universe; // For null constructor - friend class ClassPrelinker; // CDS + friend class AOTConstantPoolResolver; private: // If you add a new field that points to any metaspace object, you // must add this field to ConstantPool::metaspace_pointers_do(). @@ -109,7 +109,8 @@ class ConstantPool : public Metadata { _has_preresolution = 1, // Flags _on_stack = 2, _is_shared = 4, - _has_dynamic_constant = 8 + _has_dynamic_constant = 8, + _is_for_method_handle_intrinsic = 16 }; u2 _flags; // old fashioned bit twiddling @@ -216,6 +217,9 @@ class ConstantPool : public Metadata { bool has_dynamic_constant() const { return (_flags & _has_dynamic_constant) != 0; } void set_has_dynamic_constant() { _flags |= _has_dynamic_constant; } + bool is_for_method_handle_intrinsic() const { return (_flags & _is_for_method_handle_intrinsic) != 0; } + void set_is_for_method_handle_intrinsic() { _flags |= _is_for_method_handle_intrinsic; } + // Klass holding pool InstanceKlass* pool_holder() const { return _pool_holder; } void set_pool_holder(InstanceKlass* k) { _pool_holder = k; } @@ -679,12 +683,14 @@ class ConstantPool : public Metadata { #if INCLUDE_CDS // CDS support objArrayOop prepare_resolved_references_for_archiving() NOT_CDS_JAVA_HEAP_RETURN_(nullptr); + void find_required_hidden_classes() NOT_CDS_JAVA_HEAP_RETURN; void add_dumped_interned_strings() NOT_CDS_JAVA_HEAP_RETURN; void remove_unshareable_info(); void restore_unshareable_info(TRAPS); private: void remove_unshareable_entries(); void remove_resolved_klass_if_non_deterministic(int cp_index); + template void iterate_archivable_resolved_references(Function function); #endif private: diff --git a/src/hotspot/share/oops/cpCache.cpp b/src/hotspot/share/oops/cpCache.cpp index 321d8add755..bcf3669c9df 100644 --- a/src/hotspot/share/oops/cpCache.cpp +++ b/src/hotspot/share/oops/cpCache.cpp @@ -23,9 +23,9 @@ */ #include "precompiled.hpp" +#include "cds/aotConstantPoolResolver.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" @@ -401,9 +401,7 @@ void ConstantPoolCache::remove_unshareable_info() { assert(CDSConfig::is_dumping_archive(), "sanity"); if (_resolved_indy_entries != nullptr) { - for (int i = 0; i < _resolved_indy_entries->length(); i++) { - resolved_indy_entry_at(i)->remove_unshareable_info(); - } + remove_resolved_indy_entries_if_non_deterministic(); } if (_resolved_field_entries != nullptr) { remove_resolved_field_entries_if_non_deterministic(); @@ -422,7 +420,7 @@ void ConstantPoolCache::remove_resolved_field_entries_if_non_deterministic() { 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)) { + if (resolved && AOTConstantPoolResolver::is_resolution_deterministic(src_cp, cp_index)) { rfi->mark_and_relocate(); archived = true; } else { @@ -436,11 +434,10 @@ void ConstantPoolCache::remove_resolved_field_entries_if_non_deterministic() { 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", + log.print("%s field CP entry [%3d]: %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()); } } @@ -457,13 +454,13 @@ void ConstantPoolCache::remove_resolved_method_entries_if_non_deterministic() { bool archived = false; bool resolved = rme->is_resolved(Bytecodes::_invokevirtual) || rme->is_resolved(Bytecodes::_invokespecial) || - rme->is_resolved(Bytecodes::_invokeinterface); + rme->is_resolved(Bytecodes::_invokeinterface) || + rme->is_resolved(Bytecodes::_invokehandle); // Just for safety -- this should not happen, but do not archive if we ever see this. - resolved &= !(rme->is_resolved(Bytecodes::_invokehandle) || - rme->is_resolved(Bytecodes::_invokestatic)); + resolved &= !(rme->is_resolved(Bytecodes::_invokestatic)); - if (resolved && can_archive_resolved_method(rme)) { + if (resolved && can_archive_resolved_method(src_cp, rme)) { rme->mark_and_relocate(src_cp); archived = true; } else { @@ -495,7 +492,41 @@ void ConstantPoolCache::remove_resolved_method_entries_if_non_deterministic() { } } -bool ConstantPoolCache::can_archive_resolved_method(ResolvedMethodEntry* method_entry) { +void ConstantPoolCache::remove_resolved_indy_entries_if_non_deterministic() { + ConstantPool* cp = constant_pool(); + ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(cp); + for (int i = 0; i < _resolved_indy_entries->length(); i++) { + ResolvedIndyEntry* rei = _resolved_indy_entries->adr_at(i); + int cp_index = rei->constant_pool_index(); + bool archived = false; + bool resolved = rei->is_resolved(); + if (resolved && AOTConstantPoolResolver::is_resolution_deterministic(src_cp, cp_index)) { + rei->mark_and_relocate(); + archived = true; + } else { + rei->remove_unshareable_info(); + } + if (resolved) { + LogStreamHandle(Trace, cds, resolve) log; + if (log.is_enabled()) { + ResourceMark rm; + int bsm = cp->bootstrap_method_ref_index_at(cp_index); + int bsm_ref = cp->method_handle_index_at(bsm); + Symbol* bsm_name = cp->uncached_name_ref_at(bsm_ref); + Symbol* bsm_signature = cp->uncached_signature_ref_at(bsm_ref); + Symbol* bsm_klass = cp->klass_name_at(cp->uncached_klass_ref_index_at(bsm_ref)); + log.print("%s indy CP entry [%3d]: %s (%d)", + (archived ? "archived" : "reverted"), + cp_index, cp->pool_holder()->name()->as_C_string(), i); + log.print(" %s %s.%s:%s", (archived ? "=>" : " "), bsm_klass->as_C_string(), + bsm_name->as_C_string(), bsm_signature->as_C_string()); + } + ArchiveBuilder::alloc_stats()->record_indy_cp_entry(archived, resolved && !archived); + } + } +} + +bool ConstantPoolCache::can_archive_resolved_method(ConstantPool* src_cp, ResolvedMethodEntry* method_entry) { InstanceKlass* pool_holder = constant_pool()->pool_holder(); if (!(pool_holder->is_shared_boot_class() || pool_holder->is_shared_platform_class() || pool_holder->is_shared_app_class())) { @@ -520,10 +551,9 @@ bool ConstantPoolCache::can_archive_resolved_method(ResolvedMethodEntry* method_ } int cp_index = method_entry->constant_pool_index(); - ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(constant_pool()); assert(src_cp->tag_at(cp_index).is_method() || src_cp->tag_at(cp_index).is_interface_method(), "sanity"); - if (!ClassPrelinker::is_resolution_deterministic(src_cp, cp_index)) { + if (!AOTConstantPoolResolver::is_resolution_deterministic(src_cp, cp_index)) { return false; } @@ -531,11 +561,16 @@ bool ConstantPoolCache::can_archive_resolved_method(ResolvedMethodEntry* method_ method_entry->is_resolved(Bytecodes::_invokevirtual) || method_entry->is_resolved(Bytecodes::_invokespecial)) { return true; + } else if (method_entry->is_resolved(Bytecodes::_invokehandle)) { + if (CDSConfig::is_dumping_invokedynamic()) { + // invokehandle depends on archived MethodType and LambdaForms. + return true; + } else { + return false; + } } else { - // invokestatic and invokehandle are not supported yet. return false; } - } #endif // INCLUDE_CDS diff --git a/src/hotspot/share/oops/cpCache.hpp b/src/hotspot/share/oops/cpCache.hpp index f95f141d845..8652409a1a4 100644 --- a/src/hotspot/share/oops/cpCache.hpp +++ b/src/hotspot/share/oops/cpCache.hpp @@ -224,8 +224,9 @@ class ConstantPoolCache: public MetaspaceObj { #if INCLUDE_CDS void remove_resolved_field_entries_if_non_deterministic(); + void remove_resolved_indy_entries_if_non_deterministic(); void remove_resolved_method_entries_if_non_deterministic(); - bool can_archive_resolved_method(ResolvedMethodEntry* method_entry); + bool can_archive_resolved_method(ConstantPool* src_cp, ResolvedMethodEntry* method_entry); #endif // RedefineClasses support diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index a9024bf4a9f..d2761667f64 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -23,6 +23,7 @@ */ #include "precompiled.hpp" +#include "cds/aotClassInitializer.hpp" #include "cds/archiveUtils.hpp" #include "cds/cdsConfig.hpp" #include "cds/cdsEnumKlass.hpp" @@ -734,6 +735,18 @@ bool InstanceKlass::is_sealed() const { _permitted_subclasses != Universe::the_empty_short_array(); } +// JLS 8.9: An enum class is either implicitly final and derives +// from java.lang.Enum, or else is implicitly sealed to its +// anonymous subclasses. This query detects both kinds. +// It does not validate the finality or +// sealing conditions: it merely checks for a super of Enum. +// This is sufficient for recognizing well-formed enums. +bool InstanceKlass::is_enum_subclass() const { + InstanceKlass* s = java_super(); + return (s == vmClasses::Enum_klass() || + (s != nullptr && s->java_super() == vmClasses::Enum_klass())); +} + bool InstanceKlass::should_be_initialized() const { return !is_initialized(); } @@ -791,6 +804,68 @@ void InstanceKlass::initialize(TRAPS) { } } +#ifdef ASSERT +void InstanceKlass::assert_no_clinit_will_run_for_aot_initialized_class() const { + assert(has_aot_initialized_mirror(), "must be"); + + InstanceKlass* s = java_super(); + if (s != nullptr) { + DEBUG_ONLY(ResourceMark rm); + assert(s->is_initialized(), "super class %s of aot-inited class %s must have been initialized", + s->external_name(), external_name()); + s->assert_no_clinit_will_run_for_aot_initialized_class(); + } + + Array* interfaces = local_interfaces(); + int len = interfaces->length(); + for (int i = 0; i < len; i++) { + InstanceKlass* intf = interfaces->at(i); + if (!intf->is_initialized()) { + ResourceMark rm; + // Note: an interface needs to be marked as is_initialized() only if + // - it has a + // - it has declared a default method. + assert(!intf->interface_needs_clinit_execution_as_super(/*also_check_supers*/false), + "uninitialized super interface %s of aot-inited class %s must not have ", + intf->external_name(), external_name()); + } + } +} +#endif + +#if INCLUDE_CDS +void InstanceKlass::initialize_with_aot_initialized_mirror(TRAPS) { + assert(has_aot_initialized_mirror(), "must be"); + assert(CDSConfig::is_loading_heap(), "must be"); + assert(CDSConfig::is_using_aot_linked_classes(), "must be"); + assert_no_clinit_will_run_for_aot_initialized_class(); + + if (is_initialized()) { + return; + } + + if (log_is_enabled(Info, cds, init)) { + ResourceMark rm; + log_info(cds, init)("%s (aot-inited)", external_name()); + } + + link_class(CHECK); + +#ifdef ASSERT + { + Handle h_init_lock(THREAD, init_lock()); + ObjectLocker ol(h_init_lock, THREAD); + assert(!is_initialized(), "sanity"); + assert(!is_being_initialized(), "sanity"); + assert(!is_in_error_state(), "sanity"); + } +#endif + + set_init_thread(THREAD); + AOTClassInitializer::call_runtime_setup(THREAD, this); + set_initialization_state_and_notify(fully_initialized, CHECK); +} +#endif bool InstanceKlass::verify_code(TRAPS) { // 1) Verify the bytecodes @@ -1578,7 +1653,10 @@ void InstanceKlass::call_class_initializer(TRAPS) { #if INCLUDE_CDS // This is needed to ensure the consistency of the archived heap objects. - if (has_archived_enum_objs()) { + if (has_aot_initialized_mirror() && CDSConfig::is_loading_heap()) { + AOTClassInitializer::call_runtime_setup(THREAD, this); + return; + } else if (has_archived_enum_objs()) { assert(is_shared(), "must be"); bool initialized = CDSEnumKlass::initialize_enum_klass(this, CHECK); if (initialized) { @@ -1607,6 +1685,47 @@ void InstanceKlass::call_class_initializer(TRAPS) { } } +// If a class that implements this interface is initialized, is the JVM required +// to first execute a method declared in this interface, +// or (if also_check_supers==true) any of the super types of this interface? +// +// JVMS 5.5. Initialization, step 7: Next, if C is a class rather than +// an interface, then let SC be its superclass and let SI1, ..., SIn +// be all superinterfaces of C (whether direct or indirect) that +// declare at least one non-abstract, non-static method. +// +// So when an interface is initialized, it does not look at its +// supers. But a proper class will ensure that all of its supers have +// run their methods, except that it disregards interfaces +// that lack a non-static concrete method (i.e., a default method). +// Therefore, you should probably call this method only when the +// current class is a super of some proper class, not an interface. +bool InstanceKlass::interface_needs_clinit_execution_as_super(bool also_check_supers) const { + assert(is_interface(), "must be"); + + if (!has_nonstatic_concrete_methods()) { + // quick check: no nonstatic concrete methods are declared by this or any super interfaces + return false; + } + + // JVMS 5.5. Initialization + // ...If C is an interface that declares a non-abstract, + // non-static method, the initialization of a class that + // implements C directly or indirectly. + if (declares_nonstatic_concrete_methods() && class_initializer() != nullptr) { + return true; + } + if (also_check_supers) { + Array* all_ifs = transitive_interfaces(); + for (int i = 0; i < all_ifs->length(); ++i) { + InstanceKlass* super_intf = all_ifs->at(i); + if (super_intf->declares_nonstatic_concrete_methods() && super_intf->class_initializer() != nullptr) { + return true; + } + } + } + return false; +} void InstanceKlass::mask_for(const methodHandle& method, int bci, InterpreterOopMap* entry_for) { @@ -2497,6 +2616,7 @@ void InstanceKlass::metaspace_pointers_do(MetaspaceClosure* it) { } } + it->push(&_nest_host); it->push(&_nest_members); it->push(&_permitted_subclasses); it->push(&_record_components); @@ -2560,8 +2680,12 @@ void InstanceKlass::remove_unshareable_info() { _methods_jmethod_ids = nullptr; _jni_ids = nullptr; _oop_map_cache = nullptr; - // clear _nest_host to ensure re-load at runtime - _nest_host = nullptr; + if (CDSConfig::is_dumping_invokedynamic() && HeapShared::is_lambda_proxy_klass(this)) { + // keep _nest_host + } else { + // clear _nest_host to ensure re-load at runtime + _nest_host = nullptr; + } init_shared_package_entry(); _dep_context_last_cleaned = 0; @@ -2700,6 +2824,18 @@ bool InstanceKlass::can_be_verified_at_dumptime() const { } return true; } + +int InstanceKlass::shared_class_loader_type() const { + if (is_shared_boot_class()) { + return ClassLoader::BOOT_LOADER; + } else if (is_shared_platform_class()) { + return ClassLoader::PLATFORM_LOADER; + } else if (is_shared_app_class()) { + return ClassLoader::APP_LOADER; + } else { + return ClassLoader::OTHER; + } +} #endif // INCLUDE_CDS #if INCLUDE_JVMTI @@ -2907,6 +3043,10 @@ ModuleEntry* InstanceKlass::module() const { return class_loader_data()->unnamed_module(); } +bool InstanceKlass::in_javabase_module() const { + return module()->name() == vmSymbols::java_base(); +} + void InstanceKlass::set_package(ClassLoaderData* loader_data, PackageEntry* pkg_entry, TRAPS) { // ensure java/ packages only loaded by boot or platform builtin loaders diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index daaa16f2059..b3283a04d44 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -323,6 +323,7 @@ class InstanceKlass: public Klass { void set_shared_loading_failed() { _misc_flags.set_shared_loading_failed(true); } #if INCLUDE_CDS + int shared_class_loader_type() const; void set_shared_class_loader_type(s2 loader_type) { _misc_flags.set_shared_class_loader_type(loader_type); } void assign_class_loader_type() { _misc_flags.assign_class_loader_type(_class_loader_data); } #endif @@ -429,6 +430,9 @@ class InstanceKlass: public Klass { } bool is_record() const; + // test for enum class (or possibly an anonymous subclass within a sealed enum) + bool is_enum_subclass() const; + // permitted subclasses Array* permitted_subclasses() const { return _permitted_subclasses; } void set_permitted_subclasses(Array* s) { _permitted_subclasses = s; } @@ -475,6 +479,7 @@ public: // package PackageEntry* package() const { return _package_entry; } ModuleEntry* module() const; + bool in_javabase_module() const; bool in_unnamed_package() const { return (_package_entry == nullptr); } void set_package(ClassLoaderData* loader_data, PackageEntry* pkg_entry, TRAPS); // If the package for the InstanceKlass is in the boot loader's package entry @@ -531,12 +536,15 @@ public: // initialization (virtuals from Klass) bool should_be_initialized() const; // means that initialize should be called + void initialize_with_aot_initialized_mirror(TRAPS); + void assert_no_clinit_will_run_for_aot_initialized_class() const NOT_DEBUG_RETURN; void initialize(TRAPS); void link_class(TRAPS); bool link_class_or_fail(TRAPS); // returns false on failure void rewrite_class(TRAPS); void link_methods(TRAPS); Method* class_initializer() const; + bool interface_needs_clinit_execution_as_super(bool also_check_supers=true) const; // reference type ReferenceType reference_type() const { return (ReferenceType)_reference_type; } diff --git a/src/hotspot/share/oops/klass.hpp b/src/hotspot/share/oops/klass.hpp index 2629be64bea..8c128ab9ce6 100644 --- a/src/hotspot/share/oops/klass.hpp +++ b/src/hotspot/share/oops/klass.hpp @@ -195,7 +195,12 @@ private: _has_archived_enum_objs = 1 << 4, // This class was not loaded from a classfile in the module image // or classpath. - _is_generated_shared_class = 1 << 5 + _is_generated_shared_class = 1 << 5, + // archived mirror already initialized by AOT-cache assembly: no further need to call + _has_aot_initialized_mirror = 1 << 6, + // If this class has been aot-inititalized, do we need to call its runtimeSetup() + // method during the production run? + _is_runtime_setup_required = 1 << 7, }; #endif @@ -377,6 +382,23 @@ protected: NOT_CDS(return false;) } + void set_has_aot_initialized_mirror() { + CDS_ONLY(_shared_class_flags |= _has_aot_initialized_mirror;) + } + bool has_aot_initialized_mirror() const { + CDS_ONLY(return (_shared_class_flags & _has_aot_initialized_mirror) != 0;) + NOT_CDS(return false;) + } + + void set_is_runtime_setup_required() { + assert(has_aot_initialized_mirror(), "sanity"); + CDS_ONLY(_shared_class_flags |= _is_runtime_setup_required;) + } + bool is_runtime_setup_required() const { + CDS_ONLY(return (_shared_class_flags & _is_runtime_setup_required) != 0;) + NOT_CDS(return false;) + } + bool is_shared() const { // shadows MetaspaceObj::is_shared)() CDS_ONLY(return (_shared_class_flags & _is_shared_class) != 0;) NOT_CDS(return false;) diff --git a/src/hotspot/share/oops/markWord.hpp b/src/hotspot/share/oops/markWord.hpp index 7d2bff1efc0..1e1b8d77a90 100644 --- a/src/hotspot/share/oops/markWord.hpp +++ b/src/hotspot/share/oops/markWord.hpp @@ -133,7 +133,9 @@ class markWord { // We store the (narrow) Klass* in the bits 43 to 64. // These are for bit-precise extraction of the narrow Klass* from the 64-bit Markword + static constexpr int klass_offset_in_bytes = 4; static constexpr int klass_shift = hash_shift + hash_bits; + static constexpr int klass_shift_at_offset = klass_shift - klass_offset_in_bytes * BitsPerByte; static constexpr int klass_bits = 22; static constexpr uintptr_t klass_mask = right_n_bits(klass_bits); static constexpr uintptr_t klass_mask_in_place = klass_mask << klass_shift; diff --git a/src/hotspot/share/oops/method.cpp b/src/hotspot/share/oops/method.cpp index 26a047449ed..917c93304c1 100644 --- a/src/hotspot/share/oops/method.cpp +++ b/src/hotspot/share/oops/method.cpp @@ -1432,6 +1432,7 @@ methodHandle Method::make_method_handle_intrinsic(vmIntrinsics::ID iid, cp->symbol_at_put(_imcp_invoke_name, name); cp->symbol_at_put(_imcp_invoke_signature, signature); cp->set_has_preresolution(); + cp->set_is_for_method_handle_intrinsic(); // decide on access bits: public or not? int flags_bits = (JVM_ACC_NATIVE | JVM_ACC_SYNTHETIC | JVM_ACC_FINAL); @@ -1480,6 +1481,16 @@ methodHandle Method::make_method_handle_intrinsic(vmIntrinsics::ID iid, return m; } +#if INCLUDE_CDS +void Method::restore_archived_method_handle_intrinsic(methodHandle m, TRAPS) { + m->link_method(m, CHECK); + + if (m->intrinsic_id() == vmIntrinsics::_linkToNative) { + m->set_interpreter_entry(m->adapter()->get_i2c_entry()); + } +} +#endif + Klass* Method::check_non_bcp_klass(Klass* klass) { if (klass != nullptr && klass->class_loader() != nullptr) { if (klass->is_objArray_klass()) diff --git a/src/hotspot/share/oops/method.hpp b/src/hotspot/share/oops/method.hpp index 232e5fb577e..cc3caccd16a 100644 --- a/src/hotspot/share/oops/method.hpp +++ b/src/hotspot/share/oops/method.hpp @@ -122,6 +122,7 @@ class Method : public Metadata { #if INCLUDE_CDS void remove_unshareable_info(); void restore_unshareable_info(TRAPS); + static void restore_archived_method_handle_intrinsic(methodHandle m, TRAPS); #endif // accessors for instance variables diff --git a/src/hotspot/share/oops/objLayout.cpp b/src/hotspot/share/oops/objLayout.cpp new file mode 100644 index 00000000000..3d18d319ed2 --- /dev/null +++ b/src/hotspot/share/oops/objLayout.cpp @@ -0,0 +1,50 @@ +/* + * Copyright Amazon.com Inc. 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. + * + */ + +#include "precompiled.hpp" +#include "oops/markWord.hpp" +#include "oops/objLayout.hpp" +#include "runtime/globals.hpp" +#include "utilities/debug.hpp" + +ObjLayout::Mode ObjLayout::_klass_mode = ObjLayout::Undefined; +int ObjLayout::_oop_base_offset_in_bytes = 0; +bool ObjLayout::_oop_has_klass_gap = false; + +void ObjLayout::initialize() { + assert(_klass_mode == Undefined, "ObjLayout initialized twice"); + if (UseCompactObjectHeaders) { + _klass_mode = Compact; + _oop_base_offset_in_bytes = sizeof(markWord); + _oop_has_klass_gap = false; + } else if (UseCompressedClassPointers) { + _klass_mode = Compressed; + _oop_base_offset_in_bytes = sizeof(markWord) + sizeof(narrowKlass); + _oop_has_klass_gap = true; + } else { + _klass_mode = Uncompressed; + _oop_base_offset_in_bytes = sizeof(markWord) + sizeof(Klass*); + _oop_has_klass_gap = false; + } +} diff --git a/src/hotspot/share/oops/objLayout.hpp b/src/hotspot/share/oops/objLayout.hpp new file mode 100644 index 00000000000..e434524d4b0 --- /dev/null +++ b/src/hotspot/share/oops/objLayout.hpp @@ -0,0 +1,66 @@ +/* + * Copyright Amazon.com Inc. 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_OOPS_OBJLAYOUT_HPP +#define SHARE_OOPS_OBJLAYOUT_HPP + +/* + * This class helps to avoid loading more than one flag in some + * operations that require checking UseCompressedClassPointers, + * UseCompactObjectHeaders and possibly more. + * + * This is important on some performance critical paths, e.g. where + * the Klass* is accessed frequently, especially by GC oop iterators + * and stack-trace builders. + */ +class ObjLayout { +public: + enum Mode { + // +UseCompactObjectHeaders (implies +UseCompressedClassPointers) + Compact, + // +UseCompressedClassPointers (-UseCompactObjectHeaders) + Compressed, + // -UseCompressedClassPointers (-UseCompactObjectHeaders) + Uncompressed, + // Not yet initialized + Undefined + }; + +private: + static Mode _klass_mode; + static int _oop_base_offset_in_bytes; + static bool _oop_has_klass_gap; + +public: + static void initialize(); + static inline Mode klass_mode(); + static inline int oop_base_offset_in_bytes() { + return _oop_base_offset_in_bytes; + } + static inline bool oop_has_klass_gap() { + return _oop_has_klass_gap; + } +}; + +#endif // SHARE_OOPS_OBJLAYOUT_HPP diff --git a/src/hotspot/share/oops/objLayout.inline.hpp b/src/hotspot/share/oops/objLayout.inline.hpp new file mode 100644 index 00000000000..677c1a933bd --- /dev/null +++ b/src/hotspot/share/oops/objLayout.inline.hpp @@ -0,0 +1,48 @@ +/* + * Copyright Amazon.com Inc. 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_OOPS_OBJLAYOUT_INLINE_HPP +#define SHARE_OOPS_OBJLAYOUT_INLINE_HPP + +#include "oops/objLayout.hpp" + +inline ObjLayout::Mode ObjLayout::klass_mode() { +#ifdef ASSERT + assert(_klass_mode != Undefined, "KlassMode not yet initialized"); + if (UseCompactObjectHeaders) { + assert(_klass_mode == Compact, "Klass mode does not match flags"); + } else if (UseCompressedClassPointers) { + assert(_klass_mode == Compressed, "Klass mode does not match flags"); + } else { + assert(_klass_mode == Uncompressed, "Klass mode does not match flags"); + } +#endif +#ifdef _LP64 + return _klass_mode; +#else + return Uncompressed; +#endif +} + +#endif // SHARE_OOPS_OBJLAYOUT_INLINE_HPP diff --git a/src/hotspot/share/oops/oop.cpp b/src/hotspot/share/oops/oop.cpp index 9385379a617..11cab4c043b 100644 --- a/src/hotspot/share/oops/oop.cpp +++ b/src/hotspot/share/oops/oop.cpp @@ -152,10 +152,6 @@ bool oopDesc::is_array_noinline() const { return is_array(); } bool oopDesc::is_objArray_noinline() const { return is_objArray(); } bool oopDesc::is_typeArray_noinline() const { return is_typeArray(); } -bool oopDesc::has_klass_gap() { - return UseCompressedClassPointers && !UseCompactObjectHeaders; -} - #if INCLUDE_CDS_JAVA_HEAP void oopDesc::set_narrow_klass(narrowKlass nk) { assert(CDSConfig::is_dumping_heap(), "Used by CDS only. Do not abuse!"); diff --git a/src/hotspot/share/oops/oop.hpp b/src/hotspot/share/oops/oop.hpp index dcf42c7343b..f52baab0de6 100644 --- a/src/hotspot/share/oops/oop.hpp +++ b/src/hotspot/share/oops/oop.hpp @@ -31,6 +31,7 @@ #include "oops/accessDecorators.hpp" #include "oops/markWord.hpp" #include "oops/metadata.hpp" +#include "oops/objLayout.hpp" #include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" @@ -324,7 +325,9 @@ class oopDesc { inline bool mark_must_be_preserved() const; inline bool mark_must_be_preserved(markWord m) const; - static bool has_klass_gap(); + inline static bool has_klass_gap() { + return ObjLayout::oop_has_klass_gap(); + } // for code generation static int mark_offset_in_bytes() { return (int)offset_of(oopDesc, _mark); } @@ -332,12 +335,8 @@ class oopDesc { #ifdef _LP64 if (UseCompactObjectHeaders) { // NOTE: The only places where this is used with compact headers are the C2 - // compiler and JVMCI, and even there we don't use it to access the (narrow)Klass* - // directly. It is used only as a placeholder to identify the special memory slice - // containing Klass* info. This value could be any value that is not a valid - // field offset. Use an offset halfway into the markWord, as the markWord is never - // partially loaded from C2 and JVMCI. - return mark_offset_in_bytes() + 4; + // compiler and JVMCI. + return mark_offset_in_bytes() + markWord::klass_offset_in_bytes; } else #endif { @@ -350,15 +349,7 @@ class oopDesc { } static int base_offset_in_bytes() { - if (UseCompactObjectHeaders) { - // With compact headers, the Klass* field is not used for the Klass* - // and is used for the object fields instead. - return sizeof(markWord); - } else if (UseCompressedClassPointers) { - return sizeof(markWord) + sizeof(narrowKlass); - } else { - return sizeof(markWord) + sizeof(Klass*); - } + return ObjLayout::oop_base_offset_in_bytes(); } // for error reporting diff --git a/src/hotspot/share/oops/oop.inline.hpp b/src/hotspot/share/oops/oop.inline.hpp index 098e6bfa90f..45902e63147 100644 --- a/src/hotspot/share/oops/oop.inline.hpp +++ b/src/hotspot/share/oops/oop.inline.hpp @@ -34,6 +34,7 @@ #include "oops/arrayOop.hpp" #include "oops/compressedKlass.inline.hpp" #include "oops/instanceKlass.hpp" +#include "oops/objLayout.inline.hpp" #include "oops/markWord.inline.hpp" #include "oops/oopsHierarchy.hpp" #include "runtime/atomic.hpp" @@ -95,43 +96,48 @@ void oopDesc::init_mark() { } Klass* oopDesc::klass() const { - if (UseCompactObjectHeaders) { - return mark().klass(); - } else if (UseCompressedClassPointers) { - return CompressedKlassPointers::decode_not_null(_metadata._compressed_klass); - } else { - return _metadata._klass; + switch (ObjLayout::klass_mode()) { + case ObjLayout::Compact: + return mark().klass(); + case ObjLayout::Compressed: + return CompressedKlassPointers::decode_not_null(_metadata._compressed_klass); + default: + return _metadata._klass; } } Klass* oopDesc::klass_or_null() const { - if (UseCompactObjectHeaders) { - return mark().klass_or_null(); - } else if (UseCompressedClassPointers) { - return CompressedKlassPointers::decode(_metadata._compressed_klass); - } else { - return _metadata._klass; + switch (ObjLayout::klass_mode()) { + case ObjLayout::Compact: + return mark().klass_or_null(); + case ObjLayout::Compressed: + return CompressedKlassPointers::decode(_metadata._compressed_klass); + default: + return _metadata._klass; } } Klass* oopDesc::klass_or_null_acquire() const { - if (UseCompactObjectHeaders) { - return mark_acquire().klass(); - } else if (UseCompressedClassPointers) { - narrowKlass narrow_klass = Atomic::load_acquire(&_metadata._compressed_klass); - return CompressedKlassPointers::decode(narrow_klass); - } else { - return Atomic::load_acquire(&_metadata._klass); + switch (ObjLayout::klass_mode()) { + case ObjLayout::Compact: + return mark_acquire().klass(); + case ObjLayout::Compressed: { + narrowKlass narrow_klass = Atomic::load_acquire(&_metadata._compressed_klass); + return CompressedKlassPointers::decode(narrow_klass); + } + default: + return Atomic::load_acquire(&_metadata._klass); } } Klass* oopDesc::klass_without_asserts() const { - if (UseCompactObjectHeaders) { - return mark().klass_without_asserts(); - } else if (UseCompressedClassPointers) { - return CompressedKlassPointers::decode_without_asserts(_metadata._compressed_klass); - } else { - return _metadata._klass; + switch (ObjLayout::klass_mode()) { + case ObjLayout::Compact: + return mark().klass_without_asserts(); + case ObjLayout::Compressed: + return CompressedKlassPointers::decode_without_asserts(_metadata._compressed_klass); + default: + return _metadata._klass; } } diff --git a/src/hotspot/share/opto/memnode.hpp b/src/hotspot/share/opto/memnode.hpp index 1ca3a4b16ce..83ac80c043f 100644 --- a/src/hotspot/share/opto/memnode.hpp +++ b/src/hotspot/share/opto/memnode.hpp @@ -541,6 +541,12 @@ public: //------------------------------LoadNKlassNode--------------------------------- // Load a narrow Klass from an object. +// With compact headers, the input address (adr) does not point at the exact +// header position where the (narrow) class pointer is located, but into the +// middle of the mark word (see oopDesc::klass_offset_in_bytes()). This node +// implicitly shifts the loaded value (markWord::klass_shift_at_offset bits) to +// extract the actual class pointer. C2's type system is agnostic on whether the +// input address directly points into the class pointer. class LoadNKlassNode : public LoadNNode { public: LoadNKlassNode(Node *c, Node *mem, Node *adr, const TypePtr *at, const TypeNarrowKlass *tk, MemOrd mo) diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index 12ed10630fc..3288f2eefe4 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -2838,7 +2838,7 @@ static void thread_entry(JavaThread* thread, TRAPS) { JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)) #if INCLUDE_CDS - if (CDSConfig::is_dumping_static_archive()) { + if (CDSConfig::allow_only_single_java_thread()) { // During java -Xshare:dump, if we allow multiple Java threads to // execute in parallel, symbols and classes may be loaded in // random orders which will make the resulting CDS archive diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index 087f426c537..c68692a2fa1 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -1075,6 +1075,16 @@ bool JvmtiExport::has_early_class_hook_env() { return false; } +bool JvmtiExport::has_early_vmstart_env() { + JvmtiEnvIterator it; + for (JvmtiEnv* env = it.first(); env != nullptr; env = it.next(env)) { + if (env->early_vmstart_env()) { + return true; + } + } + return false; +} + bool JvmtiExport::_should_post_class_file_load_hook = false; // This flag is read by C2 during VM internal objects allocation diff --git a/src/hotspot/share/prims/jvmtiExport.hpp b/src/hotspot/share/prims/jvmtiExport.hpp index e0fb84f2c03..324e437411a 100644 --- a/src/hotspot/share/prims/jvmtiExport.hpp +++ b/src/hotspot/share/prims/jvmtiExport.hpp @@ -371,6 +371,7 @@ class JvmtiExport : public AllStatic { } static bool is_early_phase() NOT_JVMTI_RETURN_(false); static bool has_early_class_hook_env() NOT_JVMTI_RETURN_(false); + static bool has_early_vmstart_env() NOT_JVMTI_RETURN_(false); // Return true if the class was modified by the hook. static bool post_class_file_load_hook(Symbol* h_name, Handle class_loader, Handle h_protection_domain, diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index e9262a48086..175a85e33fb 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -46,6 +46,7 @@ #include "nmt/nmtCommon.hpp" #include "oops/compressedKlass.hpp" #include "oops/instanceKlass.hpp" +#include "oops/objLayout.hpp" #include "oops/oop.inline.hpp" #include "prims/jvmtiAgentList.hpp" #include "prims/jvmtiExport.hpp" @@ -2533,19 +2534,23 @@ jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, bool* patch_m // -Xshare:dump } else if (match_option(option, "-Xshare:dump")) { CDSConfig::enable_dumping_static_archive(); + CDSConfig::set_old_cds_flags_used(); // -Xshare:on } else if (match_option(option, "-Xshare:on")) { UseSharedSpaces = true; RequireSharedSpaces = true; + CDSConfig::set_old_cds_flags_used(); // -Xshare:auto || -XX:ArchiveClassesAtExit= } else if (match_option(option, "-Xshare:auto")) { UseSharedSpaces = true; RequireSharedSpaces = false; xshare_auto_cmd_line = true; + CDSConfig::set_old_cds_flags_used(); // -Xshare:off } else if (match_option(option, "-Xshare:off")) { UseSharedSpaces = false; RequireSharedSpaces = false; + CDSConfig::set_old_cds_flags_used(); // -Xverify } else if (match_option(option, "-Xverify", &tail)) { if (strcmp(tail, ":all") == 0 || strcmp(tail, "") == 0) { @@ -3673,6 +3678,7 @@ jint Arguments::parse(const JavaVMInitArgs* initial_cmd_args) { if (UseCompactObjectHeaders && !UseCompressedClassPointers) { FLAG_SET_DEFAULT(UseCompressedClassPointers, true); } + ObjLayout::initialize(); #endif return JNI_OK; diff --git a/src/hotspot/share/runtime/flags/jvmFlagAccess.cpp b/src/hotspot/share/runtime/flags/jvmFlagAccess.cpp index 8d9cbe33d12..843ee24c909 100644 --- a/src/hotspot/share/runtime/flags/jvmFlagAccess.cpp +++ b/src/hotspot/share/runtime/flags/jvmFlagAccess.cpp @@ -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 @@ -310,6 +310,17 @@ JVMFlag::Error JVMFlagAccess::set_impl(JVMFlag* flag, void* value, JVMFlagOrigin JVMFlag::Error JVMFlagAccess::set_ccstr(JVMFlag* flag, ccstr* value, JVMFlagOrigin origin) { if (flag == nullptr) return JVMFlag::INVALID_FLAG; if (!flag->is_ccstr()) return JVMFlag::WRONG_FORMAT; + const JVMTypedFlagLimit* constraint = (const JVMTypedFlagLimit*)JVMFlagLimit::get_constraint(flag); + if (constraint != nullptr && constraint->phase() <= JVMFlagLimit::validating_phase()) { + bool verbose = JVMFlagLimit::verbose_checks_needed() | (origin == JVMFlagOrigin::ERGONOMIC); + JVMFlag::Error err = ((JVMFlagConstraintFunc_ccstr)constraint->constraint_func())(*value, verbose); + if (err != JVMFlag::SUCCESS) { + if (origin == JVMFlagOrigin::ERGONOMIC) { + fatal("FLAG_SET_ERGO cannot be used to set an invalid value for %s", flag->name()); + } + return err; + } + } ccstr old_value = flag->get_ccstr(); trace_flag_changed(flag, old_value, *value, origin); char* new_value = nullptr; diff --git a/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.cpp b/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.cpp index 1c02e929113..354e828372f 100644 --- a/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.cpp +++ b/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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,21 @@ #include "runtime/task.hpp" #include "utilities/powerOfTwo.hpp" +JVMFlag::Error AOTModeConstraintFunc(ccstr value, bool verbose) { + if (strcmp(value, "off") != 0 && + strcmp(value, "record") != 0 && + strcmp(value, "create") != 0 && + strcmp(value, "auto") != 0 && + strcmp(value, "on") != 0) { + JVMFlag::printError(verbose, + "Unrecognized value %s for AOTMode. Must be one of the following: " + "off, record, create, auto, on\n", + value); + return JVMFlag::VIOLATES_CONSTRAINT; + } + + return JVMFlag::SUCCESS; +} JVMFlag::Error ObjectAlignmentInBytesConstraintFunc(int value, bool verbose) { if (!is_power_of_2(value)) { JVMFlag::printError(verbose, diff --git a/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.hpp b/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.hpp index cbe28456b8a..20d420a8e7e 100644 --- a/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.hpp +++ b/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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,7 @@ */ #define RUNTIME_CONSTRAINTS(f) \ + f(ccstr, AOTModeConstraintFunc) \ f(int, ObjectAlignmentInBytesConstraintFunc) \ f(int, ContendedPaddingWidthConstraintFunc) \ f(int, PerfDataSamplingIntervalFunc) \ diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index 29386a0c368..41ac7bc6627 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -24,12 +24,15 @@ */ #include "precompiled.hpp" +#include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" +#include "cds/heapShared.hpp" #include "cds/metaspaceShared.hpp" #include "classfile/classLoader.hpp" #include "classfile/javaClasses.hpp" #include "classfile/javaThreadStatus.hpp" +#include "classfile/symbolTable.hpp" #include "classfile/systemDictionary.hpp" #include "classfile/vmClasses.hpp" #include "classfile/vmSymbols.hpp" @@ -350,12 +353,15 @@ void Threads::initialize_java_lang_classes(JavaThread* main_thread, TRAPS) { initialize_class(vmSymbols::java_lang_System(), CHECK); // The VM creates & returns objects of this class. Make sure it's initialized. initialize_class(vmSymbols::java_lang_Class(), CHECK); + initialize_class(vmSymbols::java_lang_ThreadGroup(), CHECK); Handle thread_group = create_initial_thread_group(CHECK); Universe::set_main_thread_group(thread_group()); initialize_class(vmSymbols::java_lang_Thread(), CHECK); create_initial_thread(thread_group, main_thread, CHECK); + HeapShared::init_box_classes(CHECK); + // The VM creates objects of this class. initialize_class(vmSymbols::java_lang_Module(), CHECK); @@ -403,6 +409,7 @@ void Threads::initialize_java_lang_classes(JavaThread* main_thread, TRAPS) { initialize_class(vmSymbols::java_lang_StackOverflowError(), CHECK); initialize_class(vmSymbols::java_lang_IllegalMonitorStateException(), CHECK); initialize_class(vmSymbols::java_lang_IllegalArgumentException(), CHECK); + initialize_class(vmSymbols::java_lang_InternalError(), CHECK); } void Threads::initialize_jsr292_core_classes(TRAPS) { @@ -412,6 +419,10 @@ void Threads::initialize_jsr292_core_classes(TRAPS) { initialize_class(vmSymbols::java_lang_invoke_ResolvedMethodName(), CHECK); initialize_class(vmSymbols::java_lang_invoke_MemberName(), CHECK); initialize_class(vmSymbols::java_lang_invoke_MethodHandleNatives(), CHECK); + + if (UseSharedSpaces) { + HeapShared::initialize_java_lang_invoke(CHECK); + } } jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { @@ -737,6 +748,11 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { } #endif + if (CDSConfig::is_using_aot_linked_classes()) { + AOTLinkedClassBulkLoader::finish_loading_javabase_classes(CHECK_JNI_ERR); + SystemDictionary::restore_archived_method_handle_intrinsics(); + } + // Start string deduplication thread if requested. if (StringDedup::is_enabled()) { StringDedup::start(); @@ -752,6 +768,13 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { // loaded until phase 2 completes call_initPhase2(CHECK_JNI_ERR); + if (CDSConfig::is_using_aot_linked_classes()) { + AOTLinkedClassBulkLoader::load_non_javabase_classes(THREAD); + } +#ifndef PRODUCT + HeapShared::initialize_test_class_from_archive(THREAD); +#endif + JFR_ONLY(Jfr::on_create_vm_2();) // Always call even when there are not JVMTI environments yet, since environments diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java index ba63f2d538f..771084384c8 100644 --- a/src/java.base/share/classes/java/lang/Class.java +++ b/src/java.base/share/classes/java/lang/Class.java @@ -225,6 +225,11 @@ public final class Class implements java.io.Serializable, private static native void registerNatives(); static { + runtimeSetup(); + } + + // Called from JVM when loading an AOT cache + private static void runtimeSetup() { registerNatives(); } @@ -3425,6 +3430,15 @@ public final class Class implements java.io.Serializable, } private static ReflectionFactory reflectionFactory; + /** + * When CDS is enabled, the Class class may be aot-initialized. However, + * we can't archive reflectionFactory, so we reset it to null, so it + * will be allocated again at runtime. + */ + private static void resetArchivedStates() { + reflectionFactory = null; + } + /** * Returns the elements of this enum class or null if this * Class object does not represent an enum class. diff --git a/src/java.base/share/classes/java/lang/invoke/LambdaFormEditor.java b/src/java.base/share/classes/java/lang/invoke/LambdaFormEditor.java index 4de0d9c95cb..8d84c5f8096 100644 --- a/src/java.base/share/classes/java/lang/invoke/LambdaFormEditor.java +++ b/src/java.base/share/classes/java/lang/invoke/LambdaFormEditor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -38,6 +38,7 @@ import static java.lang.invoke.LambdaForm.BasicType.*; import static java.lang.invoke.MethodHandleImpl.Intrinsic; import static java.lang.invoke.MethodHandleImpl.NF_loop; import static java.lang.invoke.MethodHandleImpl.makeIntrinsic; +import static java.lang.invoke.MethodHandleNatives.USE_SOFT_CACHE; /** Transforms on LFs. * A lambda-form editor can derive new LFs from its base LF. @@ -89,12 +90,17 @@ class LambdaFormEditor { * Tightly coupled with the TransformKey class, which is used to lookup existing * Transforms. */ - private static final class Transform extends SoftReference { + private static final class Transform { + final Object cache; final long packedBytes; final byte[] fullBytes; private Transform(long packedBytes, byte[] fullBytes, LambdaForm result) { - super(result); + if (USE_SOFT_CACHE) { + cache = new SoftReference(result); + } else { + cache = result; + } this.packedBytes = packedBytes; this.fullBytes = fullBytes; } @@ -135,6 +141,15 @@ class LambdaFormEditor { } return buf.toString(); } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public LambdaForm get() { + if (cache instanceof LambdaForm lf) { + return lf; + } else { + return ((SoftReference)cache).get(); + } + } } /** diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java index ac9765ddc3b..4ffbf826317 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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 @@ -28,9 +28,11 @@ package java.lang.invoke; import jdk.internal.misc.VM; import jdk.internal.ref.CleanerFactory; import sun.invoke.util.Wrapper; +import sun.security.action.GetPropertyAction; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.Field; +import java.util.Properties; import static java.lang.invoke.MethodHandleNatives.Constants.*; import static java.lang.invoke.MethodHandleStatics.TRACE_METHOD_LINKAGE; @@ -690,4 +692,23 @@ class MethodHandleNatives { return (definingClass.isAssignableFrom(symbolicRefClass) || // Msym overrides Mdef symbolicRefClass.isInterface()); // Mdef implements Msym } + + //--- AOTCache support + + /** + * In normal execution, this is set to true, so that LambdaFormEditor and MethodTypeForm will + * use soft references to allow class unloading. + * + * When dumping the AOTCache, this is set to false so that no cached heap objects will + * contain soft references (which are not yet supported by AOTCache - see JDK-8341587). AOTCache + * only stores LambdaFormEditors and MethodTypeForms for classes in the boot/platform/app loaders. + * Such classes will never be unloaded, so it's OK to use hard references. + */ + static final boolean USE_SOFT_CACHE; + + static { + Properties props = GetPropertyAction.privilegedGetProperties(); + USE_SOFT_CACHE = Boolean.parseBoolean( + props.getProperty("java.lang.invoke.MethodHandleNatives.USE_SOFT_CACHE", "true")); + } } diff --git a/src/java.base/share/classes/java/lang/invoke/MethodType.java b/src/java.base/share/classes/java/lang/invoke/MethodType.java index 2b3843b09e3..5e1c0f8581c 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodType.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodType.java @@ -31,6 +31,8 @@ import java.lang.constant.MethodTypeDesc; import java.util.Arrays; import java.util.Collections; import java.util.function.Supplier; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -40,6 +42,7 @@ import java.util.concurrent.ConcurrentHashMap; import jdk.internal.util.ReferencedKeySet; import jdk.internal.util.ReferenceKey; +import jdk.internal.misc.CDS; import jdk.internal.vm.annotation.Stable; import sun.invoke.util.BytecodeDescriptor; import sun.invoke.util.VerifyType; @@ -391,6 +394,17 @@ class MethodType ptypes = NO_PTYPES; trusted = true; } MethodType primordialMT = new MethodType(rtype, ptypes); + if (archivedMethodTypes != null) { + // If this JVM process reads from archivedMethodTypes, it never + // modifies the table. So there's no need for synchronization. + // See copyInternTable() below. + assert CDS.isUsingArchive(); + MethodType mt = archivedMethodTypes.get(primordialMT); + if (mt != null) { + return mt; + } + } + MethodType mt = internTable.get(primordialMT); if (mt != null) return mt; @@ -409,7 +423,9 @@ class MethodType mt.form = MethodTypeForm.findForm(mt); return internTable.intern(mt); } + private static final @Stable MethodType[] objectOnlyTypes = new MethodType[20]; + private static @Stable HashMap archivedMethodTypes; /** * Finds or creates a method type whose components are {@code Object} with an optional trailing {@code Object[]} array. @@ -1380,4 +1396,30 @@ s.writeObject(this.parameterArray()); wrapAlt = null; return mt; } + + static HashMap copyInternTable() { + HashMap copy = new HashMap<>(); + + for (Iterator i = internTable.iterator(); i.hasNext(); ) { + MethodType t = i.next(); + copy.put(t, t); + } + + return copy; + } + + // This is called from C code, at the very end of Java code execution + // during the AOT cache assembly phase. + static void createArchivedObjects() { + // After the archivedMethodTypes field is assigned, this table + // is never modified. So we don't need synchronization when reading from + // it (which happens only in a future JVM process, never in the current process). + // + // @implNote CDS.isDumpingStaticArchive() is mutually exclusive with + // CDS.isUsingArchive(); at most one of them can return true for any given JVM + // process. + assert CDS.isDumpingStaticArchive(); + archivedMethodTypes = copyInternTable(); + internTable.clear(); + } } diff --git a/src/java.base/share/classes/java/lang/invoke/MethodTypeForm.java b/src/java.base/share/classes/java/lang/invoke/MethodTypeForm.java index 09377618797..69545478ec4 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodTypeForm.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodTypeForm.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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 @@ -30,6 +30,7 @@ import sun.invoke.util.Wrapper; import java.lang.ref.SoftReference; import static java.lang.invoke.MethodHandleStatics.newIllegalArgumentException; +import static java.lang.invoke.MethodHandleNatives.USE_SOFT_CACHE; /** * Shared information for a group of method types, which differ @@ -51,7 +52,7 @@ final class MethodTypeForm { final MethodType basicType; // the canonical erasure, with primitives simplified // Cached adapter information: - final SoftReference[] methodHandles; + private final Object[] methodHandles; // Indexes into methodHandles: static final int @@ -61,7 +62,7 @@ final class MethodTypeForm { MH_LIMIT = 3; // Cached lambda form information, for basic types only: - final SoftReference[] lambdaForms; + private final Object[] lambdaForms; // Indexes into lambdaForms: static final int @@ -109,39 +110,55 @@ final class MethodTypeForm { return basicType; } + @SuppressWarnings({"rawtypes", "unchecked"}) public MethodHandle cachedMethodHandle(int which) { - SoftReference entry = methodHandles[which]; - return (entry != null) ? entry.get() : null; + Object entry = methodHandles[which]; + if (entry == null) { + return null; + } else if (entry instanceof MethodHandle mh) { + return mh; + } else { + return ((SoftReference)entry).get(); + } } public synchronized MethodHandle setCachedMethodHandle(int which, MethodHandle mh) { // Simulate a CAS, to avoid racy duplication of results. - SoftReference entry = methodHandles[which]; - if (entry != null) { - MethodHandle prev = entry.get(); - if (prev != null) { - return prev; - } + MethodHandle prev = cachedMethodHandle(which); + if (prev != null) { + return prev; + } + if (USE_SOFT_CACHE) { + methodHandles[which] = new SoftReference<>(mh); + } else { + methodHandles[which] = mh; } - methodHandles[which] = new SoftReference<>(mh); return mh; } + @SuppressWarnings({"rawtypes", "unchecked"}) public LambdaForm cachedLambdaForm(int which) { - SoftReference entry = lambdaForms[which]; - return (entry != null) ? entry.get() : null; + Object entry = lambdaForms[which]; + if (entry == null) { + return null; + } else if (entry instanceof LambdaForm lf) { + return lf; + } else { + return ((SoftReference)entry).get(); + } } public synchronized LambdaForm setCachedLambdaForm(int which, LambdaForm form) { // Simulate a CAS, to avoid racy duplication of results. - SoftReference entry = lambdaForms[which]; - if (entry != null) { - LambdaForm prev = entry.get(); - if (prev != null) { - return prev; - } + LambdaForm prev = cachedLambdaForm(which); + if (prev != null) { + return prev; + } + if (USE_SOFT_CACHE) { + lambdaForms[which] = new SoftReference<>(form); + } else { + lambdaForms[which] = form; } - lambdaForms[which] = new SoftReference<>(form); return form; } @@ -191,8 +208,8 @@ final class MethodTypeForm { this.primitiveCount = primitiveCount; this.parameterSlotCount = (short)pslotCount; - this.lambdaForms = new SoftReference[LF_LIMIT]; - this.methodHandles = new SoftReference[MH_LIMIT]; + this.lambdaForms = new Object[LF_LIMIT]; + this.methodHandles = new Object[MH_LIMIT]; } else { this.basicType = MethodType.methodType(basicReturnType, basicPtypes, true); // fill in rest of data from the basic type: diff --git a/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java b/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java index 2f5b8f2407e..7e91057cbf4 100644 --- a/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java +++ b/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java @@ -1080,6 +1080,8 @@ public final class StringConcatFactory { * without copying. */ private static final class InlineHiddenClassStrategy { + // The CLASS_NAME prefix must be the same as used by HeapShared::is_string_concat_klass() + // in the HotSpot code. static final String CLASS_NAME = "java.lang.String$$StringConcat"; static final String METHOD_NAME = "concat"; diff --git a/src/java.base/share/classes/java/lang/module/ModuleFinder.java b/src/java.base/share/classes/java/lang/module/ModuleFinder.java index 6b2e9228ad5..bc470633039 100644 --- a/src/java.base/share/classes/java/lang/module/ModuleFinder.java +++ b/src/java.base/share/classes/java/lang/module/ModuleFinder.java @@ -26,9 +26,6 @@ package java.lang.module; import java.nio.file.Path; -import java.security.AccessController; -import java.security.Permission; -import java.security.PrivilegedAction; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -130,16 +127,8 @@ public interface ModuleFinder { * * @return A {@code ModuleFinder} that locates the system modules */ - @SuppressWarnings("removal") static ModuleFinder ofSystem() { - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - sm.checkPermission(new RuntimePermission("accessSystemModules")); - PrivilegedAction pa = SystemModuleFinders::ofSystem; - return AccessController.doPrivileged(pa); - } else { - return SystemModuleFinders.ofSystem(); - } + return SystemModuleFinders.ofSystem(); } /** diff --git a/src/java.base/share/classes/java/net/Authenticator.java b/src/java.base/share/classes/java/net/Authenticator.java index 0935366abc1..9ac504f9a62 100644 --- a/src/java.base/share/classes/java/net/Authenticator.java +++ b/src/java.base/share/classes/java/net/Authenticator.java @@ -112,14 +112,6 @@ class Authenticator { * any previously set authenticator is removed. */ public static synchronized void setDefault(Authenticator a) { - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - NetPermission setDefaultPermission - = new NetPermission("setDefaultAuthenticator"); - sm.checkPermission(setDefaultPermission); - } - theAuthenticator = a; } @@ -130,13 +122,6 @@ class Authenticator { * @since 9 */ public static Authenticator getDefault() { - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - NetPermission requestPermission - = new NetPermission("requestPasswordAuthentication"); - sm.checkPermission(requestPermission); - } return theAuthenticator; } @@ -161,14 +146,6 @@ class Authenticator { String prompt, String scheme) { - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - NetPermission requestPermission - = new NetPermission("requestPasswordAuthentication"); - sm.checkPermission(requestPermission); - } - Authenticator a = theAuthenticator; if (a == null) { return null; @@ -212,14 +189,6 @@ class Authenticator { String prompt, String scheme) { - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - NetPermission requestPermission - = new NetPermission("requestPasswordAuthentication"); - sm.checkPermission(requestPermission); - } - Authenticator a = theAuthenticator; if (a == null) { return null; @@ -267,14 +236,6 @@ class Authenticator { URL url, RequestorType reqType) { - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - NetPermission requestPermission - = new NetPermission("requestPasswordAuthentication"); - sm.checkPermission(requestPermission); - } - Authenticator a = theAuthenticator; if (a == null) { return null; @@ -328,14 +289,6 @@ class Authenticator { URL url, RequestorType reqType) { - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - NetPermission requestPermission - = new NetPermission("requestPasswordAuthentication"); - sm.checkPermission(requestPermission); - } - Authenticator a = authenticator == null ? theAuthenticator : authenticator; if (a == null) { return null; diff --git a/src/java.base/share/classes/java/net/CookieHandler.java b/src/java.base/share/classes/java/net/CookieHandler.java index 4b5e3ebedd8..ec20c860591 100644 --- a/src/java.base/share/classes/java/net/CookieHandler.java +++ b/src/java.base/share/classes/java/net/CookieHandler.java @@ -28,7 +28,6 @@ package java.net; import java.util.Map; import java.util.List; import java.io.IOException; -import sun.security.util.SecurityConstants; /** * A CookieHandler object provides a callback mechanism to hook up a @@ -73,11 +72,6 @@ public abstract class CookieHandler { * @see #setDefault(CookieHandler) */ public static synchronized CookieHandler getDefault() { - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - sm.checkPermission(SecurityConstants.GET_COOKIEHANDLER_PERMISSION); - } return cookieHandler; } @@ -91,11 +85,6 @@ public abstract class CookieHandler { * @see #getDefault() */ public static synchronized void setDefault(CookieHandler cHandler) { - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - sm.checkPermission(SecurityConstants.SET_COOKIEHANDLER_PERMISSION); - } cookieHandler = cHandler; } diff --git a/src/java.base/share/classes/java/net/ProxySelector.java b/src/java.base/share/classes/java/net/ProxySelector.java index ee1f9ef3710..613fe2aa176 100644 --- a/src/java.base/share/classes/java/net/ProxySelector.java +++ b/src/java.base/share/classes/java/net/ProxySelector.java @@ -28,8 +28,6 @@ package java.net; import java.io.IOException; import java.util.List; -import sun.security.util.SecurityConstants; - /** * Selects the proxy server to use, if any, when connecting to the * network resource referenced by a URL. A proxy selector is a @@ -94,11 +92,6 @@ public abstract class ProxySelector { * @since 1.5 */ public static ProxySelector getDefault() { - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - sm.checkPermission(SecurityConstants.GET_PROXYSELECTOR_PERMISSION); - } return theProxySelector; } @@ -114,11 +107,6 @@ public abstract class ProxySelector { * @since 1.5 */ public static void setDefault(ProxySelector ps) { - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - sm.checkPermission(SecurityConstants.SET_PROXYSELECTOR_PERMISSION); - } theProxySelector = ps; } diff --git a/src/java.base/share/classes/java/net/ResponseCache.java b/src/java.base/share/classes/java/net/ResponseCache.java index 292c1b7610d..3339f41883b 100644 --- a/src/java.base/share/classes/java/net/ResponseCache.java +++ b/src/java.base/share/classes/java/net/ResponseCache.java @@ -28,7 +28,6 @@ package java.net; import java.io.IOException; import java.util.Map; import java.util.List; -import sun.security.util.SecurityConstants; /** * Represents implementations of URLConnection caches. An instance of @@ -84,11 +83,6 @@ public abstract class ResponseCache { * @since 1.5 */ public static synchronized ResponseCache getDefault() { - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - sm.checkPermission(SecurityConstants.GET_RESPONSECACHE_PERMISSION); - } return theResponseCache; } @@ -104,11 +98,6 @@ public abstract class ResponseCache { * @since 1.5 */ public static synchronized void setDefault(ResponseCache responseCache) { - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - sm.checkPermission(SecurityConstants.SET_RESPONSECACHE_PERMISSION); - } theResponseCache = responseCache; } diff --git a/src/java.base/share/classes/java/util/Arrays.java b/src/java.base/share/classes/java/util/Arrays.java index 589c027b2dc..03fdf7a3c99 100644 --- a/src/java.base/share/classes/java/util/Arrays.java +++ b/src/java.base/share/classes/java/util/Arrays.java @@ -985,11 +985,8 @@ public final class Arrays { * circular dependencies. To be removed in a future release. */ static final class LegacyMergeSort { - @SuppressWarnings("removal") private static final boolean userRequested = - java.security.AccessController.doPrivileged( - new sun.security.action.GetBooleanAction( - "java.util.Arrays.useLegacyMergeSort")).booleanValue(); + Boolean.getBoolean("java.util.Arrays.useLegacyMergeSort"); } /** diff --git a/src/java.base/share/classes/java/util/Calendar.java b/src/java.base/share/classes/java/util/Calendar.java index 7ac98c02297..7cf389df42b 100644 --- a/src/java.base/share/classes/java/util/Calendar.java +++ b/src/java.base/share/classes/java/util/Calendar.java @@ -43,12 +43,6 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OptionalDataException; import java.io.Serializable; -import java.security.AccessControlContext; -import java.security.AccessController; -import java.security.PermissionCollection; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.security.ProtectionDomain; import java.text.DateFormat; import java.text.DateFormatSymbols; import java.time.Instant; @@ -3564,25 +3558,9 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable() { - @Override - public ZoneInfo run() throws Exception { - return (ZoneInfo) input.readObject(); - } - }, - CalendarAccessControlContext.INSTANCE); - } catch (PrivilegedActionException pae) { - Exception e = pae.getException(); + zi = (ZoneInfo) input.readObject(); + } catch (Exception e) { if (!(e instanceof OptionalDataException)) { if (e instanceof RuntimeException) { throw (RuntimeException) e; diff --git a/src/java.base/share/classes/java/util/Currency.java b/src/java.base/share/classes/java/util/Currency.java index 0bc86f3281e..789eefad5d5 100644 --- a/src/java.base/share/classes/java/util/Currency.java +++ b/src/java.base/share/classes/java/util/Currency.java @@ -32,8 +32,6 @@ import java.io.FileReader; import java.io.InputStream; import java.io.IOException; import java.io.Serializable; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.concurrent.ConcurrentHashMap; @@ -213,63 +211,57 @@ public final class Currency implements Serializable { initStatic(); } - @SuppressWarnings("removal") private static void initStatic() { - AccessController.doPrivileged(new PrivilegedAction<>() { - @Override - public Void run() { - try { - try (InputStream in = getClass().getResourceAsStream("/java/util/currency.data")) { - if (in == null) { - throw new InternalError("Currency data not found"); - } - DataInputStream dis = new DataInputStream(new BufferedInputStream(in)); - if (dis.readInt() != MAGIC_NUMBER) { - throw new InternalError("Currency data is possibly corrupted"); - } - formatVersion = dis.readInt(); - if (formatVersion != VALID_FORMAT_VERSION) { - throw new InternalError("Currency data format is incorrect"); - } - dataVersion = dis.readInt(); - mainTable = readIntArray(dis, A_TO_Z * A_TO_Z); - int scCount = dis.readInt(); - specialCasesList = readSpecialCases(dis, scCount); - int ocCount = dis.readInt(); - otherCurrenciesList = readOtherCurrencies(dis, ocCount); - } - } catch (IOException e) { - throw new InternalError(e); - } - // look for the properties file for overrides - String propsFile = System.getProperty("java.util.currency.data"); - if (propsFile == null) { - propsFile = StaticProperty.javaHome() + File.separator + "lib" + - File.separator + "currency.properties"; + try { + try (InputStream in = Currency.class.getResourceAsStream("/java/util/currency.data")) { + if (in == null) { + throw new InternalError("Currency data not found"); } - try { - File propFile = new File(propsFile); - if (propFile.exists()) { - Properties props = new Properties(); - try (FileReader fr = new FileReader(propFile)) { - props.load(fr); - } - Pattern propertiesPattern = - Pattern.compile("([A-Z]{3})\\s*,\\s*(\\d{3})\\s*,\\s*" + - "(\\d+)\\s*,?\\s*(\\d{4}-\\d{2}-\\d{2}T\\d{2}:" + - "\\d{2}:\\d{2})?"); - List currencyEntries - = getValidCurrencyData(props, propertiesPattern); - currencyEntries.forEach(Currency::replaceCurrencyData); - } - } catch (IOException e) { - CurrencyProperty.info("currency.properties is ignored" - + " because of an IOException", e); + DataInputStream dis = new DataInputStream(new BufferedInputStream(in)); + if (dis.readInt() != MAGIC_NUMBER) { + throw new InternalError("Currency data is possibly corrupted"); } - return null; + formatVersion = dis.readInt(); + if (formatVersion != VALID_FORMAT_VERSION) { + throw new InternalError("Currency data format is incorrect"); + } + dataVersion = dis.readInt(); + mainTable = readIntArray(dis, A_TO_Z * A_TO_Z); + int scCount = dis.readInt(); + specialCasesList = readSpecialCases(dis, scCount); + int ocCount = dis.readInt(); + otherCurrenciesList = readOtherCurrencies(dis, ocCount); } - }); + } catch (IOException e) { + throw new InternalError(e); + } + + // look for the properties file for overrides + String propsFile = System.getProperty("java.util.currency.data"); + if (propsFile == null) { + propsFile = StaticProperty.javaHome() + File.separator + "lib" + + File.separator + "currency.properties"; + } + try { + File propFile = new File(propsFile); + if (propFile.exists()) { + Properties props = new Properties(); + try (FileReader fr = new FileReader(propFile)) { + props.load(fr); + } + Pattern propertiesPattern = + Pattern.compile("([A-Z]{3})\\s*,\\s*(\\d{3})\\s*,\\s*" + + "(\\d+)\\s*,?\\s*(\\d{4}-\\d{2}-\\d{2}T\\d{2}:" + + "\\d{2}:\\d{2})?"); + List currencyEntries + = getValidCurrencyData(props, propertiesPattern); + currencyEntries.forEach(Currency::replaceCurrencyData); + } + } catch (IOException e) { + CurrencyProperty.info("currency.properties is ignored" + + " because of an IOException", e); + } } /** @@ -494,10 +486,7 @@ public final class Currency implements Serializable { } } } - - @SuppressWarnings("unchecked") - Set result = (Set) available.clone(); - return result; + return new HashSet<>(available); } /** diff --git a/src/java.base/share/classes/java/util/Locale.java b/src/java.base/share/classes/java/util/Locale.java index 600b20de639..c055c160367 100644 --- a/src/java.base/share/classes/java/util/Locale.java +++ b/src/java.base/share/classes/java/util/Locale.java @@ -1216,10 +1216,6 @@ public final class Locale implements Cloneable, Serializable { if (newLocale == null) throw new NullPointerException("Can't set default locale to NULL"); - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) sm.checkPermission(new PropertyPermission - ("user.language", "write")); switch (category) { case DISPLAY: defaultDisplayLocale = newLocale; diff --git a/src/java.base/share/classes/java/util/Properties.java b/src/java.base/share/classes/java/util/Properties.java index 45ca0363660..015cdbc7107 100644 --- a/src/java.base/share/classes/java/util/Properties.java +++ b/src/java.base/share/classes/java/util/Properties.java @@ -949,9 +949,6 @@ public class Properties extends Hashtable { } private static void writeDateComment(BufferedWriter bw) throws IOException { - // value of java.properties.date system property isn't sensitive - // and so doesn't need any security manager checks to make the value accessible - // to the callers String sysPropVal = StaticProperty.javaPropertiesDate(); if (sysPropVal != null && !sysPropVal.isEmpty()) { writeComments(bw, sysPropVal); diff --git a/src/java.base/share/classes/java/util/PropertyResourceBundle.java b/src/java.base/share/classes/java/util/PropertyResourceBundle.java index 02391fa2de2..aba65cbcd88 100644 --- a/src/java.base/share/classes/java/util/PropertyResourceBundle.java +++ b/src/java.base/share/classes/java/util/PropertyResourceBundle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 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 @@ -46,7 +46,6 @@ import java.io.IOException; import java.nio.charset.MalformedInputException; import java.nio.charset.UnmappableCharacterException; import sun.nio.cs.ISO_8859_1; -import sun.security.action.GetPropertyAction; import sun.util.PropertyResourceBundleCharset; import sun.util.ResourceBundleEnumeration; @@ -132,9 +131,9 @@ public class PropertyResourceBundle extends ResourceBundle { // Check whether the strict encoding is specified. // The possible encoding is either "ISO-8859-1" or "UTF-8". - private static final String encoding = GetPropertyAction - .privilegedGetProperty("java.util.PropertyResourceBundle.encoding", "") - .toUpperCase(Locale.ROOT); + private static final String encoding = + System.getProperty("java.util.PropertyResourceBundle.encoding", "") + .toUpperCase(Locale.ROOT); /** * Creates a property resource bundle from an {@link java.io.InputStream diff --git a/src/java.base/share/classes/java/util/ResourceBundle.java b/src/java.base/share/classes/java/util/ResourceBundle.java index 83f58b8506f..989cc09f388 100644 --- a/src/java.base/share/classes/java/util/ResourceBundle.java +++ b/src/java.base/share/classes/java/util/ResourceBundle.java @@ -42,7 +42,6 @@ package java.util; import java.io.IOException; import java.io.InputStream; -import java.io.UncheckedIOException; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; @@ -53,10 +52,6 @@ import java.lang.reflect.Modifier; import java.net.JarURLConnection; import java.net.URL; import java.net.URLConnection; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.jar.JarEntry; @@ -70,12 +65,9 @@ import jdk.internal.access.SharedSecrets; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; import jdk.internal.util.ReferencedKeyMap; -import sun.security.action.GetPropertyAction; import sun.util.locale.BaseLocale; import sun.util.resources.Bundles; -import static sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION; - /** * @@ -581,10 +573,8 @@ public abstract class ResourceBundle { return locale; } - @SuppressWarnings("removal") private static ClassLoader getLoader(Module module) { - PrivilegedAction pa = module::getClassLoader; - return AccessController.doPrivileged(pa); + return module.getClassLoader(); } /** @@ -1506,15 +1496,12 @@ public abstract class ResourceBundle { } private static class ResourceBundleControlProviderHolder { - private static final PrivilegedAction> pa = - () -> ServiceLoader.load(ResourceBundleControlProvider.class, - ClassLoader.getSystemClassLoader()).stream() - .map(ServiceLoader.Provider::get) - .toList(); - @SuppressWarnings("removal") private static final List CONTROL_PROVIDERS = - AccessController.doPrivileged(pa); + ServiceLoader.load(ResourceBundleControlProvider.class, + ClassLoader.getSystemClassLoader()).stream() + .map(ServiceLoader.Provider::get) + .toList(); private static Control getControl(String baseName) { return CONTROL_PROVIDERS.isEmpty() ? @@ -1594,13 +1581,6 @@ public abstract class ResourceBundle { Control control) { Objects.requireNonNull(module); Module callerModule = getCallerModule(caller); - if (callerModule != module) { - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - sm.checkPermission(GET_CLASSLOADER_PERMISSION); - } - } return getBundleImpl(callerModule, module, baseName, locale, control); } @@ -1885,7 +1865,6 @@ public abstract class ResourceBundle { * Returns the service type of the given baseName that is visible * to the given class loader */ - @SuppressWarnings("removal") private static Class getResourceBundleProviderType(String baseName, ClassLoader loader) { @@ -1900,27 +1879,20 @@ public abstract class ResourceBundle { // Use the class loader of the getBundle caller so that the caller's // visibility of the provider type is checked. - return AccessController.doPrivileged( - new PrivilegedAction<>() { - @Override - public Class run() { - try { - Class c = Class.forName(providerName, false, loader); - if (ResourceBundleProvider.class.isAssignableFrom(c)) { - @SuppressWarnings("unchecked") - Class s = (Class) c; - return s; - } - } catch (ClassNotFoundException e) {} - return null; - } - }); + try { + Class c = Class.forName(providerName, false, loader); + if (ResourceBundleProvider.class.isAssignableFrom(c)) { + @SuppressWarnings("unchecked") + Class s = (Class) c; + return s; + } + } catch (ClassNotFoundException _) {} + return null; } /** * Loads ResourceBundle from service providers. */ - @SuppressWarnings("removal") private static ResourceBundle loadBundleFromProviders(String baseName, Locale locale, ServiceLoader providers, @@ -1928,34 +1900,28 @@ public abstract class ResourceBundle { { if (providers == null) return null; - return AccessController.doPrivileged( - new PrivilegedAction<>() { - public ResourceBundle run() { - for (Iterator itr = providers.iterator(); itr.hasNext(); ) { - try { - ResourceBundleProvider provider = itr.next(); - if (cacheKey != null && cacheKey.callerHasProvider == null - && cacheKey.getModule() == provider.getClass().getModule()) { - cacheKey.callerHasProvider = Boolean.TRUE; - } - ResourceBundle bundle = provider.getBundle(baseName, locale); - trace("provider %s %s locale: %s bundle: %s%n", provider, baseName, locale, bundle); - if (bundle != null) { - return bundle; - } - } catch (ServiceConfigurationError | SecurityException e) { - if (cacheKey != null) { - cacheKey.setCause(e); - } - } - } - if (cacheKey != null && cacheKey.callerHasProvider == null) { - cacheKey.callerHasProvider = Boolean.FALSE; - } - return null; - } - }); - + for (Iterator itr = providers.iterator(); itr.hasNext(); ) { + try { + ResourceBundleProvider provider = itr.next(); + if (cacheKey != null && cacheKey.callerHasProvider == null + && cacheKey.getModule() == provider.getClass().getModule()) { + cacheKey.callerHasProvider = Boolean.TRUE; + } + ResourceBundle bundle = provider.getBundle(baseName, locale); + trace("provider %s %s locale: %s bundle: %s%n", provider, baseName, locale, bundle); + if (bundle != null) { + return bundle; + } + } catch (ServiceConfigurationError e) { + if (cacheKey != null) { + cacheKey.setCause(e); + } + } + } + if (cacheKey != null && cacheKey.callerHasProvider == null) { + cacheKey.callerHasProvider = Boolean.FALSE; + } + return null; } /* @@ -3153,7 +3119,6 @@ public abstract class ResourceBundle { return bundle; } - @SuppressWarnings("removal") private ResourceBundle newBundle0(String bundleName, String format, ClassLoader loader, boolean reload) throws IllegalAccessException, InstantiationException, IOException { @@ -3177,28 +3142,20 @@ public abstract class ResourceBundle { bundleClass.getName() + " in " + m.toString()); } try { - Constructor ctor = AccessController.doPrivileged( - new PrivilegedExceptionAction<>() { - @Override - public Constructor run() throws NoSuchMethodException { - return bundleClass.getDeclaredConstructor(); - } - }); + Constructor ctor = bundleClass.getDeclaredConstructor(); if (!Modifier.isPublic(ctor.getModifiers())) { throw new IllegalAccessException("no-arg constructor in " + bundleClass.getName() + " is not publicly accessible."); } // java.base may not be able to read the bundleClass's module. - PrivilegedAction pa1 = () -> { ctor.setAccessible(true); return null; }; - AccessController.doPrivileged(pa1); + ctor.setAccessible(true); bundle = ctor.newInstance((Object[]) null); } catch (InvocationTargetException e) { uncheckedThrow(e); - } catch (PrivilegedActionException e) { - assert e.getCause() instanceof NoSuchMethodException; + } catch (NoSuchMethodException e) { throw new InstantiationException("public no-arg constructor " + - "does not exist in " + bundleClass.getName()); + "does not exist in " + bundleClass.getName()); } } else { throw new ClassCastException(c.getName() @@ -3212,27 +3169,16 @@ public abstract class ResourceBundle { return bundle; } - final boolean reloadFlag = reload; - InputStream stream = null; - try { - stream = AccessController.doPrivileged( - new PrivilegedExceptionAction<>() { - public InputStream run() throws IOException { - URL url = loader.getResource(resourceName); - if (url == null) return null; + URL url = loader.getResource(resourceName); + if (url == null) return null; - URLConnection connection = url.openConnection(); - if (reloadFlag) { - // Disable caches to get fresh data for - // reloading. - connection.setUseCaches(false); - } - return connection.getInputStream(); - } - }); - } catch (PrivilegedActionException e) { - throw (IOException) e.getCause(); + URLConnection connection = url.openConnection(); + if (reload) { + // Disable caches to get fresh data for + // reloading. + connection.setUseCaches(false); } + InputStream stream = connection.getInputStream(); if (stream != null) { try { bundle = new PropertyResourceBundle(stream); @@ -3563,7 +3509,6 @@ public abstract class ResourceBundle { /** * Returns a new ResourceBundle instance of the given bundleClass */ - @SuppressWarnings("removal") static ResourceBundle newResourceBundle(Class bundleClass) { try { @SuppressWarnings("unchecked") @@ -3573,8 +3518,7 @@ public abstract class ResourceBundle { return null; } // java.base may not be able to read the bundleClass's module. - PrivilegedAction pa = () -> { ctor.setAccessible(true); return null;}; - AccessController.doPrivileged(pa); + ctor.setAccessible(true); try { return ctor.newInstance((Object[]) null); } catch (InvocationTargetException e) { @@ -3602,9 +3546,7 @@ public abstract class ResourceBundle { { String bundleName = Control.INSTANCE.toBundleName(baseName, locale); try { - PrivilegedAction> pa = () -> Class.forName(module, bundleName); - @SuppressWarnings("removal") - Class c = AccessController.doPrivileged(pa, null, GET_CLASSLOADER_PERMISSION); + Class c = Class.forName(module, bundleName); trace("local in %s %s caller %s: %s%n", module, bundleName, callerModule, c); if (c == null) { @@ -3662,56 +3604,46 @@ public abstract class ResourceBundle { { String bundleName = Control.INSTANCE.toBundleName(baseName, locale); - PrivilegedAction pa = () -> { - try { - String resourceName = Control.INSTANCE - .toResourceName0(bundleName, "properties"); - if (resourceName == null) { - return null; - } - trace("local in %s %s caller %s%n", module, resourceName, callerModule); + String resourceName = Control.INSTANCE + .toResourceName0(bundleName, "properties"); + if (resourceName == null) { + return null; + } + trace("local in %s %s caller %s%n", module, resourceName, callerModule); - // if the package is in the given module but not opened - // locate it from the given module first. - String pn = toPackageName(bundleName); - trace(" %s/%s is accessible to %s : %s%n", - module.getName(), pn, callerModule, - isAccessible(callerModule, module, pn)); - if (isAccessible(callerModule, module, pn)) { - InputStream in = module.getResourceAsStream(resourceName); - if (in != null) { - return in; - } - } - ClassLoader loader = module.getClassLoader(); - trace("loader for %s %s caller %s%n", module, resourceName, callerModule); - - try { - if (loader != null) { - return loader.getResourceAsStream(resourceName); - } else { - URL url = BootLoader.findResource(resourceName); - if (url != null) { - return url.openStream(); - } - } - } catch (Exception e) {} - return null; - - } catch (IOException e) { - throw new UncheckedIOException(e); + // if the package is in the given module but not opened + // locate it from the given module first. + String pn = toPackageName(bundleName); + trace(" %s/%s is accessible to %s : %s%n", + module.getName(), pn, callerModule, + isAccessible(callerModule, module, pn)); + if (isAccessible(callerModule, module, pn)) { + InputStream in = module.getResourceAsStream(resourceName); + if (in != null) { + return new PropertyResourceBundle(in); } - }; + } + ClassLoader loader = module.getClassLoader(); + trace("loader for %s %s caller %s%n", module, resourceName, callerModule); - try (@SuppressWarnings("removal") InputStream stream = AccessController.doPrivileged(pa)) { + try { + InputStream stream = null; + if (loader != null) { + stream = loader.getResourceAsStream(resourceName); + } else { + URL url = BootLoader.findResource(resourceName); + if (url != null) { + stream = url.openStream(); + } + } if (stream != null) { return new PropertyResourceBundle(stream); } else { return null; } - } catch (UncheckedIOException e) { - throw e.getCause(); + } catch (Exception e) { + return null; } } @@ -3722,8 +3654,8 @@ public abstract class ResourceBundle { } - private static final boolean TRACE_ON = Boolean.parseBoolean( - GetPropertyAction.privilegedGetProperty("resource.bundle.debug", "false")); + private static final boolean TRACE_ON = Boolean.getBoolean( + System.getProperty("resource.bundle.debug", "false")); private static void trace(String format, Object... params) { if (TRACE_ON) diff --git a/src/java.base/share/classes/java/util/TimeZone.java b/src/java.base/share/classes/java/util/TimeZone.java index f1a2d92b6ba..f0b122418c9 100644 --- a/src/java.base/share/classes/java/util/TimeZone.java +++ b/src/java.base/share/classes/java/util/TimeZone.java @@ -43,7 +43,6 @@ import java.time.ZoneId; import java.time.ZoneOffset; import jdk.internal.util.StaticProperty; -import sun.security.action.GetPropertyAction; import sun.util.calendar.ZoneInfo; import sun.util.calendar.ZoneInfoFile; import sun.util.locale.provider.TimeZoneNameUtility; @@ -683,7 +682,7 @@ public abstract class TimeZone implements Serializable, Cloneable { private static synchronized TimeZone setDefaultZone() { TimeZone tz; // get the time zone ID from the system properties - Properties props = GetPropertyAction.privilegedGetProperties(); + Properties props = System.getProperties(); String zoneID = props.getProperty("user.timezone"); // if the time zone ID is not set (yet), perform the @@ -729,12 +728,6 @@ public abstract class TimeZone implements Serializable, Cloneable { */ public static void setDefault(TimeZone zone) { - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - sm.checkPermission(new PropertyPermission - ("user.timezone", "write")); - } // by saving a defensive clone and returning a clone in getDefault() too, // the defaultTimeZone instance is isolated from user code which makes it // effectively immutable. This is important to avoid races when the diff --git a/src/java.base/share/classes/java/util/Tripwire.java b/src/java.base/share/classes/java/util/Tripwire.java index c807a419619..ec20b956375 100644 --- a/src/java.base/share/classes/java/util/Tripwire.java +++ b/src/java.base/share/classes/java/util/Tripwire.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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,9 +26,6 @@ package java.util; import sun.util.logging.PlatformLogger; -import java.security.AccessController; -import java.security.PrivilegedAction; - /** * Utility class for detecting inadvertent uses of boxing in * {@code java.util} classes. The detection is turned on or off based on @@ -49,9 +46,7 @@ final class Tripwire { private static final String TRIPWIRE_PROPERTY = "org.openjdk.java.util.stream.tripwire"; /** Should debugging checks be enabled? */ - @SuppressWarnings("removal") - static final boolean ENABLED = AccessController.doPrivileged( - (PrivilegedAction) () -> Boolean.getBoolean(TRIPWIRE_PROPERTY)); + static final boolean ENABLED = Boolean.getBoolean(TRIPWIRE_PROPERTY); private Tripwire() { } diff --git a/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java b/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java index 5a23fdb05a6..53919011c6f 100644 --- a/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java +++ b/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java @@ -70,6 +70,7 @@ import java.util.function.ToLongFunction; import java.util.stream.Stream; import jdk.internal.misc.Unsafe; import jdk.internal.util.ArraysSupport; +import jdk.internal.vm.annotation.Stable; /** * A hash table supporting full concurrency of retrievals and @@ -595,7 +596,16 @@ public class ConcurrentHashMap extends AbstractMap static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash /** Number of CPUS, to place bounds on some sizings */ - static final int NCPU = Runtime.getRuntime().availableProcessors(); + static @Stable int NCPU; + + static { + runtimeSetup(); + } + + // Called from JVM when loading an AOT cache. + private static void runtimeSetup() { + NCPU = Runtime.getRuntime().availableProcessors(); + } /** * Serialized pseudo-fields, provided only for jdk7 compatibility. diff --git a/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListSet.java b/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListSet.java index 2a2fbc54d86..0bcb7978e90 100644 --- a/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListSet.java +++ b/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListSet.java @@ -527,20 +527,11 @@ public class ConcurrentSkipListSet /** Initializes map field; for use in clone. */ private void setMap(ConcurrentNavigableMap map) { - @SuppressWarnings("removal") - Field mapField = java.security.AccessController.doPrivileged( - (java.security.PrivilegedAction) () -> { - try { - Field f = ConcurrentSkipListSet.class - .getDeclaredField("m"); - f.setAccessible(true); - return f; - } catch (ReflectiveOperationException e) { - throw new Error(e); - }}); try { + Field mapField = ConcurrentSkipListSet.class.getDeclaredField("m"); + mapField.setAccessible(true); mapField.set(this, map); - } catch (IllegalAccessException e) { + } catch (IllegalAccessException | NoSuchFieldException e) { throw new Error(e); } } diff --git a/src/java.base/share/classes/java/util/concurrent/CopyOnWriteArrayList.java b/src/java.base/share/classes/java/util/concurrent/CopyOnWriteArrayList.java index f0f60730eb6..f5069c61d45 100644 --- a/src/java.base/share/classes/java/util/concurrent/CopyOnWriteArrayList.java +++ b/src/java.base/share/classes/java/util/concurrent/CopyOnWriteArrayList.java @@ -2096,20 +2096,11 @@ public class CopyOnWriteArrayList /** Initializes the lock; for use when deserializing or cloning. */ private void resetLock() { - @SuppressWarnings("removal") - Field lockField = java.security.AccessController.doPrivileged( - (java.security.PrivilegedAction) () -> { - try { - Field f = CopyOnWriteArrayList.class - .getDeclaredField("lock"); - f.setAccessible(true); - return f; - } catch (ReflectiveOperationException e) { - throw new Error(e); - }}); try { + Field lockField = CopyOnWriteArrayList.class.getDeclaredField("lock"); + lockField.setAccessible(true); lockField.set(this, new Object()); - } catch (IllegalAccessException e) { + } catch (IllegalAccessException | NoSuchFieldException e) { throw new Error(e); } } diff --git a/src/java.base/share/classes/java/util/concurrent/Executors.java b/src/java.base/share/classes/java/util/concurrent/Executors.java index a0a25b1a70d..f804e225790 100644 --- a/src/java.base/share/classes/java/util/concurrent/Executors.java +++ b/src/java.base/share/classes/java/util/concurrent/Executors.java @@ -37,16 +37,12 @@ package java.util.concurrent; import static java.lang.ref.Reference.reachabilityFence; import java.lang.ref.Cleaner.Cleanable; -import java.security.AccessControlContext; -import java.security.AccessController; import java.security.PrivilegedAction; -import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.Collection; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import jdk.internal.ref.CleanerFactory; -import sun.security.util.SecurityConstants; /** * Factory and utility methods for {@link Executor}, {@link @@ -559,27 +555,13 @@ public class Executors { */ private static final class PrivilegedCallable implements Callable { final Callable task; - @SuppressWarnings("removal") - final AccessControlContext acc; - @SuppressWarnings("removal") PrivilegedCallable(Callable task) { this.task = task; - this.acc = AccessController.getContext(); } - @SuppressWarnings("removal") public T call() throws Exception { - try { - return AccessController.doPrivileged( - new PrivilegedExceptionAction() { - public T run() throws Exception { - return task.call(); - } - }, acc); - } catch (PrivilegedActionException e) { - throw e.getException(); - } + return task.call(); } public String toString() { @@ -595,49 +577,26 @@ public class Executors { implements Callable { final Callable task; @SuppressWarnings("removal") - final AccessControlContext acc; final ClassLoader ccl; @SuppressWarnings("removal") PrivilegedCallableUsingCurrentClassLoader(Callable task) { - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - // Calls to getContextClassLoader from this class - // never trigger a security check, but we check - // whether our callers have this permission anyways. - sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION); - - // Whether setContextClassLoader turns out to be necessary - // or not, we fail fast if permission is not available. - sm.checkPermission(new RuntimePermission("setContextClassLoader")); - } this.task = task; - this.acc = AccessController.getContext(); this.ccl = Thread.currentThread().getContextClassLoader(); } - @SuppressWarnings("removal") public T call() throws Exception { - try { - return AccessController.doPrivileged( - new PrivilegedExceptionAction() { - public T run() throws Exception { - Thread t = Thread.currentThread(); - ClassLoader cl = t.getContextClassLoader(); - if (ccl == cl) { - return task.call(); - } else { - t.setContextClassLoader(ccl); - try { - return task.call(); - } finally { - t.setContextClassLoader(cl); - } - } - } - }, acc); - } catch (PrivilegedActionException e) { - throw e.getException(); + Thread t = Thread.currentThread(); + ClassLoader cl = t.getContextClassLoader(); + if (ccl == cl) { + return task.call(); + } else { + t.setContextClassLoader(ccl); + try { + return task.call(); + } finally { + t.setContextClassLoader(cl); + } } } @@ -656,10 +615,7 @@ public class Executors { private final String namePrefix; DefaultThreadFactory() { - @SuppressWarnings("removal") - SecurityManager s = System.getSecurityManager(); - group = (s != null) ? s.getThreadGroup() : - Thread.currentThread().getThreadGroup(); + group = Thread.currentThread().getThreadGroup(); namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-"; @@ -678,27 +634,14 @@ public class Executors { } /** - * Thread factory capturing access control context and class loader. + * Thread factory capturing the current class loader. */ private static class PrivilegedThreadFactory extends DefaultThreadFactory { @SuppressWarnings("removal") - final AccessControlContext acc; final ClassLoader ccl; - @SuppressWarnings("removal") PrivilegedThreadFactory() { super(); - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - // Calls to getContextClassLoader from this class - // never trigger a security check, but we check - // whether our callers have this permission anyways. - sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION); - - // Fail fast - sm.checkPermission(new RuntimePermission("setContextClassLoader")); - } - this.acc = AccessController.getContext(); this.ccl = Thread.currentThread().getContextClassLoader(); } @@ -706,13 +649,8 @@ public class Executors { return super.newThread(new Runnable() { @SuppressWarnings("removal") public void run() { - AccessController.doPrivileged(new PrivilegedAction<>() { - public Void run() { - Thread.currentThread().setContextClassLoader(ccl); - r.run(); - return null; - } - }, acc); + Thread.currentThread().setContextClassLoader(ccl); + r.run(); } }); } @@ -811,9 +749,7 @@ public class Executors { super(executor); Runnable action = () -> { if (!executor.isShutdown()) { - PrivilegedAction pa = () -> { executor.shutdown(); return null; }; - @SuppressWarnings("removal") - var ignore = AccessController.doPrivileged(pa); + executor.shutdown(); } }; cleanable = CleanerFactory.cleaner().register(this, action); diff --git a/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java b/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java index 17448e9bf3d..41c6b4914b9 100644 --- a/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java +++ b/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java @@ -26,8 +26,6 @@ package java.util.concurrent; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.time.Duration; import java.time.Instant; import java.util.Objects; @@ -688,7 +686,7 @@ public class StructuredTaskScope implements AutoCloseable { /** * Interrupt all unfinished threads. */ - private void implInterruptAll() { + private void interruptAll() { flock.threads() .filter(t -> t != Thread.currentThread()) .forEach(t -> { @@ -698,19 +696,6 @@ public class StructuredTaskScope implements AutoCloseable { }); } - @SuppressWarnings("removal") - private void interruptAll() { - if (System.getSecurityManager() == null) { - implInterruptAll(); - } else { - PrivilegedAction pa = () -> { - implInterruptAll(); - return null; - }; - AccessController.doPrivileged(pa); - } - } - /** * Shutdown the task scope if not already shutdown. Return true if this method * shutdowns the task scope, false if already shutdown. diff --git a/src/java.base/share/classes/java/util/concurrent/ThreadLocalRandom.java b/src/java.base/share/classes/java/util/concurrent/ThreadLocalRandom.java index 751c190acc0..19eb3122947 100644 --- a/src/java.base/share/classes/java/util/concurrent/ThreadLocalRandom.java +++ b/src/java.base/share/classes/java/util/concurrent/ThreadLocalRandom.java @@ -39,7 +39,6 @@ package java.util.concurrent; import java.io.ObjectStreamField; -import java.security.AccessControlContext; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; diff --git a/src/java.base/share/classes/java/util/concurrent/ThreadPerTaskExecutor.java b/src/java.base/share/classes/java/util/concurrent/ThreadPerTaskExecutor.java index e98ece084c0..f37ef6c956e 100644 --- a/src/java.base/share/classes/java/util/concurrent/ThreadPerTaskExecutor.java +++ b/src/java.base/share/classes/java/util/concurrent/ThreadPerTaskExecutor.java @@ -26,7 +26,6 @@ package java.util.concurrent; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; -import java.security.Permission; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -48,7 +47,6 @@ import jdk.internal.vm.ThreadContainers; */ class ThreadPerTaskExecutor extends ThreadContainer implements ExecutorService { private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); - private static final Permission MODIFY_THREAD = new RuntimePermission("modifyThread"); private static final VarHandle STATE = MhUtil.findVarHandle( MethodHandles.lookup(), "state", int.class); @@ -80,18 +78,6 @@ class ThreadPerTaskExecutor extends ThreadContainer implements ExecutorService { return executor; } - /** - * Throws SecurityException if there is a security manager set and it denies - * RuntimePermission("modifyThread"). - */ - @SuppressWarnings("removal") - private void checkPermission() { - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - sm.checkPermission(MODIFY_THREAD); - } - } - /** * Throws RejectedExecutionException if the executor has been shutdown. */ @@ -143,14 +129,12 @@ class ThreadPerTaskExecutor extends ThreadContainer implements ExecutorService { @Override public void shutdown() { - checkPermission(); if (!isShutdown()) tryShutdownAndTerminate(false); } @Override public List shutdownNow() { - checkPermission(); if (!isTerminated()) tryShutdownAndTerminate(true); return List.of(); @@ -202,7 +186,6 @@ class ThreadPerTaskExecutor extends ThreadContainer implements ExecutorService { @Override public void close() { - checkPermission(); awaitTermination(); } diff --git a/src/java.base/share/classes/java/util/concurrent/ThreadPoolExecutor.java b/src/java.base/share/classes/java/util/concurrent/ThreadPoolExecutor.java index 65217849f19..e34810fe966 100644 --- a/src/java.base/share/classes/java/util/concurrent/ThreadPoolExecutor.java +++ b/src/java.base/share/classes/java/util/concurrent/ThreadPoolExecutor.java @@ -566,29 +566,6 @@ public class ThreadPoolExecutor extends AbstractExecutorService { private static final RejectedExecutionHandler defaultHandler = new AbortPolicy(); - /** - * Permission required for callers of shutdown and shutdownNow. - * We additionally require (see checkShutdownAccess) that callers - * have permission to actually interrupt threads in the worker set - * (as governed by Thread.interrupt, which relies on - * ThreadGroup.checkAccess, which in turn relies on - * SecurityManager.checkAccess). Shutdowns are attempted only if - * these checks pass. - * - * All actual invocations of Thread.interrupt (see - * interruptIdleWorkers and interruptWorkers) ignore - * SecurityExceptions, meaning that the attempted interrupts - * silently fail. In the case of shutdown, they should not fail - * unless the SecurityManager has inconsistent policies, sometimes - * allowing access to a thread and sometimes not. In such cases, - * failure to actually interrupt threads may disable or delay full - * termination. Other uses of interruptIdleWorkers are advisory, - * and failure to actually interrupt will merely delay response to - * configuration changes so is not handled exceptionally. - */ - private static final RuntimePermission shutdownPerm = - new RuntimePermission("modifyThread"); - /** * Class Worker mainly maintains interrupt control state for * threads running tasks, along with other minor bookkeeping. @@ -673,10 +650,7 @@ public class ThreadPoolExecutor extends AbstractExecutorService { void interruptIfStarted() { Thread t; if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { - try { - t.interrupt(); - } catch (SecurityException ignore) { - } + t.interrupt(); } } } @@ -749,27 +723,7 @@ public class ThreadPoolExecutor extends AbstractExecutorService { */ /** - * If there is a security manager, makes sure caller has - * permission to shut down threads in general (see shutdownPerm). - * If this passes, additionally makes sure the caller is allowed - * to interrupt each worker thread. This might not be true even if - * first check passed, if the SecurityManager treats some threads - * specially. - */ - private void checkShutdownAccess() { - // assert mainLock.isHeldByCurrentThread(); - @SuppressWarnings("removal") - SecurityManager security = System.getSecurityManager(); - if (security != null) { - security.checkPermission(shutdownPerm); - for (Worker w : workers) - security.checkAccess(w.thread); - } - } - - /** - * Interrupts all threads, even if active. Ignores SecurityExceptions - * (in which case some threads may remain uninterrupted). + * Interrupts all threads, even if active. */ private void interruptWorkers() { // assert mainLock.isHeldByCurrentThread(); @@ -780,9 +734,7 @@ public class ThreadPoolExecutor extends AbstractExecutorService { /** * Interrupts threads that might be waiting for tasks (as * indicated by not being locked) so they can check for - * termination or configuration changes. Ignores - * SecurityExceptions (in which case some threads may remain - * uninterrupted). + * termination or configuration changes. * * @param onlyOne If true, interrupt at most one worker. This is * called only from tryTerminate when termination is otherwise @@ -805,7 +757,6 @@ public class ThreadPoolExecutor extends AbstractExecutorService { if (!t.isInterrupted() && w.tryLock()) { try { t.interrupt(); - } catch (SecurityException ignore) { } finally { w.unlock(); } @@ -1390,7 +1341,6 @@ public class ThreadPoolExecutor extends AbstractExecutorService { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { - checkShutdownAccess(); advanceRunState(SHUTDOWN); interruptIdleWorkers(); onShutdown(); // hook for ScheduledThreadPoolExecutor @@ -1420,7 +1370,6 @@ public class ThreadPoolExecutor extends AbstractExecutorService { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { - checkShutdownAccess(); advanceRunState(STOP); interruptWorkers(); tasks = drainQueue(); diff --git a/src/java.base/share/classes/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.java b/src/java.base/share/classes/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.java index 6d412315153..e01b3ec7d50 100644 --- a/src/java.base/share/classes/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.java +++ b/src/java.base/share/classes/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.java @@ -37,9 +37,6 @@ package java.util.concurrent.atomic; import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; import java.util.function.IntBinaryOperator; import java.util.function.IntUnaryOperator; import jdk.internal.misc.Unsafe; @@ -385,30 +382,16 @@ public abstract class AtomicIntegerFieldUpdater { /** class holding the field */ private final Class tclass; - @SuppressWarnings("removal") AtomicIntegerFieldUpdaterImpl(final Class tclass, final String fieldName, final Class caller) { final Field field; final int modifiers; try { - field = AccessController.doPrivileged( - new PrivilegedExceptionAction() { - public Field run() throws NoSuchFieldException { - return tclass.getDeclaredField(fieldName); - } - }); + field = tclass.getDeclaredField(fieldName); modifiers = field.getModifiers(); sun.reflect.misc.ReflectUtil.ensureMemberAccess( caller, tclass, null, modifiers); - ClassLoader cl = tclass.getClassLoader(); - ClassLoader ccl = caller.getClassLoader(); - if ((ccl != null) && (ccl != cl) && - ((cl == null) || !isAncestor(cl, ccl))) { - sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass); - } - } catch (PrivilegedActionException pae) { - throw new RuntimeException(pae.getException()); } catch (Exception ex) { throw new RuntimeException(ex); } diff --git a/src/java.base/share/classes/java/util/concurrent/atomic/AtomicLongFieldUpdater.java b/src/java.base/share/classes/java/util/concurrent/atomic/AtomicLongFieldUpdater.java index 0e496dbd6a5..57722f33371 100644 --- a/src/java.base/share/classes/java/util/concurrent/atomic/AtomicLongFieldUpdater.java +++ b/src/java.base/share/classes/java/util/concurrent/atomic/AtomicLongFieldUpdater.java @@ -37,9 +37,6 @@ package java.util.concurrent.atomic; import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; import java.util.function.LongBinaryOperator; import java.util.function.LongUnaryOperator; import jdk.internal.misc.Unsafe; @@ -381,29 +378,15 @@ public abstract class AtomicLongFieldUpdater { /** class holding the field */ private final Class tclass; - @SuppressWarnings("removal") CASUpdater(final Class tclass, final String fieldName, final Class caller) { final Field field; final int modifiers; try { - field = AccessController.doPrivileged( - new PrivilegedExceptionAction() { - public Field run() throws NoSuchFieldException { - return tclass.getDeclaredField(fieldName); - } - }); + field = tclass.getDeclaredField(fieldName); modifiers = field.getModifiers(); sun.reflect.misc.ReflectUtil.ensureMemberAccess( caller, tclass, null, modifiers); - ClassLoader cl = tclass.getClassLoader(); - ClassLoader ccl = caller.getClassLoader(); - if ((ccl != null) && (ccl != cl) && - ((cl == null) || !isAncestor(cl, ccl))) { - sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass); - } - } catch (PrivilegedActionException pae) { - throw new RuntimeException(pae.getException()); } catch (Exception ex) { throw new RuntimeException(ex); } diff --git a/src/java.base/share/classes/java/util/concurrent/atomic/AtomicReferenceArray.java b/src/java.base/share/classes/java/util/concurrent/atomic/AtomicReferenceArray.java index ec00f40a568..dbec6b81dd9 100644 --- a/src/java.base/share/classes/java/util/concurrent/atomic/AtomicReferenceArray.java +++ b/src/java.base/share/classes/java/util/concurrent/atomic/AtomicReferenceArray.java @@ -329,20 +329,12 @@ public class AtomicReferenceArray implements java.io.Serializable { throw new java.io.InvalidObjectException("Not array type"); if (a.getClass() != Object[].class) a = Arrays.copyOf((Object[])a, Array.getLength(a), Object[].class); - @SuppressWarnings("removal") - Field arrayField = java.security.AccessController.doPrivileged( - (java.security.PrivilegedAction) () -> { - try { - Field f = AtomicReferenceArray.class - .getDeclaredField("array"); - f.setAccessible(true); - return f; - } catch (ReflectiveOperationException e) { - throw new Error(e); - }}); try { + + Field arrayField = AtomicReferenceArray.class.getDeclaredField("array"); + arrayField.setAccessible(true); arrayField.set(this, a); - } catch (IllegalAccessException e) { + } catch (NoSuchFieldException | IllegalAccessException e) { throw new Error(e); } } diff --git a/src/java.base/share/classes/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java b/src/java.base/share/classes/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java index b9dcdac9ba5..1c0c6d0afd0 100644 --- a/src/java.base/share/classes/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java +++ b/src/java.base/share/classes/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java @@ -37,9 +37,6 @@ package java.util.concurrent.atomic; import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; import java.util.function.BinaryOperator; import java.util.function.UnaryOperator; import jdk.internal.misc.Unsafe; @@ -320,7 +317,6 @@ public abstract class AtomicReferenceFieldUpdater { * screenings fail. */ - @SuppressWarnings("removal") AtomicReferenceFieldUpdaterImpl(final Class tclass, final Class vclass, final String fieldName, @@ -329,24 +325,11 @@ public abstract class AtomicReferenceFieldUpdater { final Class fieldClass; final int modifiers; try { - field = AccessController.doPrivileged( - new PrivilegedExceptionAction() { - public Field run() throws NoSuchFieldException { - return tclass.getDeclaredField(fieldName); - } - }); + field = tclass.getDeclaredField(fieldName); modifiers = field.getModifiers(); sun.reflect.misc.ReflectUtil.ensureMemberAccess( caller, tclass, null, modifiers); - ClassLoader cl = tclass.getClassLoader(); - ClassLoader ccl = caller.getClassLoader(); - if ((ccl != null) && (ccl != cl) && - ((cl == null) || !isAncestor(cl, ccl))) { - sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass); - } fieldClass = field.getType(); - } catch (PrivilegedActionException pae) { - throw new RuntimeException(pae.getException()); } catch (Exception ex) { throw new RuntimeException(ex); } diff --git a/src/java.base/share/classes/java/util/concurrent/atomic/Striped64.java b/src/java.base/share/classes/java/util/concurrent/atomic/Striped64.java index 04ae2b45158..ad230f62ab6 100644 --- a/src/java.base/share/classes/java/util/concurrent/atomic/Striped64.java +++ b/src/java.base/share/classes/java/util/concurrent/atomic/Striped64.java @@ -380,17 +380,12 @@ abstract class Striped64 extends Number { BASE = MhUtil.findVarHandle(l1, "base", long.class); CELLSBUSY = MhUtil.findVarHandle(l1, "cellsBusy", int.class); - @SuppressWarnings("removal") - MethodHandles.Lookup l2 = java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction<>() { - public MethodHandles.Lookup run() { - try { - return MethodHandles.privateLookupIn(Thread.class, MethodHandles.lookup()); - } catch (ReflectiveOperationException e) { - throw new ExceptionInInitializerError(e); - } - }}); - THREAD_PROBE = MhUtil.findVarHandle(l2, "threadLocalRandomProbe", int.class); + try { + MethodHandles.Lookup l2 = MethodHandles.privateLookupIn(Thread.class, l1); + THREAD_PROBE = MhUtil.findVarHandle(l2, "threadLocalRandomProbe", int.class); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } } } diff --git a/src/java.base/share/classes/java/util/spi/AbstractResourceBundleProvider.java b/src/java.base/share/classes/java/util/spi/AbstractResourceBundleProvider.java index 6eb35aed691..8db27ec586d 100644 --- a/src/java.base/share/classes/java/util/spi/AbstractResourceBundleProvider.java +++ b/src/java.base/share/classes/java/util/spi/AbstractResourceBundleProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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,12 +32,9 @@ import sun.util.resources.Bundles; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.Locale; import java.util.PropertyResourceBundle; import java.util.ResourceBundle; -import static sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION; /** * {@code AbstractResourceBundleProvider} is an abstract class that provides @@ -78,11 +75,10 @@ import static sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION; * return null; * } * }} - * + *

* Refer to {@link ResourceBundleProvider} for details. * - * @see - * Resource Bundles and Named Modules + * @see ResourceBundle##resource-bundle-modules Resource Bundles and Named Modules * @since 9 */ public abstract class AbstractResourceBundleProvider implements ResourceBundleProvider { @@ -222,11 +218,8 @@ public abstract class AbstractResourceBundleProvider implements ResourceBundlePr * Returns the ResourceBundle of .class format if found in the module * of this provider. */ - private static ResourceBundle loadResourceBundle(Module module, String bundleName) - { - PrivilegedAction> pa = () -> Class.forName(module, bundleName); - @SuppressWarnings("removal") - Class c = AccessController.doPrivileged(pa, null, GET_CLASSLOADER_PERMISSION); + private static ResourceBundle loadResourceBundle(Module module, String bundleName) { + Class c = Class.forName(module, bundleName); if (c != null && ResourceBundle.class.isAssignableFrom(c)) { @SuppressWarnings("unchecked") Class bundleClass = (Class) c; @@ -241,28 +234,17 @@ public abstract class AbstractResourceBundleProvider implements ResourceBundlePr */ private static ResourceBundle loadPropertyResourceBundle(Module module, String bundleName) - throws IOException - { + throws IOException { String resourceName = toResourceName(bundleName, "properties"); if (resourceName == null) { return null; } - PrivilegedAction pa = () -> { - try { - return module.getResourceAsStream(resourceName); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }; - try (@SuppressWarnings("removal") InputStream stream = AccessController.doPrivileged(pa)) { - if (stream != null) { - return new PropertyResourceBundle(stream); - } else { - return null; - } - } catch (UncheckedIOException e) { - throw e.getCause(); + InputStream stream = module.getResourceAsStream(resourceName); + if (stream != null) { + return new PropertyResourceBundle(stream); + } else { + return null; } } diff --git a/src/java.base/share/classes/java/util/spi/LocaleServiceProvider.java b/src/java.base/share/classes/java/util/spi/LocaleServiceProvider.java index 46712e5c80b..6e130872bd4 100644 --- a/src/java.base/share/classes/java/util/spi/LocaleServiceProvider.java +++ b/src/java.base/share/classes/java/util/spi/LocaleServiceProvider.java @@ -222,22 +222,10 @@ import java.util.Locale; */ public abstract class LocaleServiceProvider { - private static Void checkPermission() { - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - sm.checkPermission(new RuntimePermission("localeServiceProvider")); - } - return null; - } - private LocaleServiceProvider(Void ignore) { } - /** * Initializes a new locale service provider. */ - protected LocaleServiceProvider() { - this(checkPermission()); - } + protected LocaleServiceProvider() {} /** * {@return an array of all locales for which this locale service provider diff --git a/src/java.base/share/classes/java/util/spi/ToolProvider.java b/src/java.base/share/classes/java/util/spi/ToolProvider.java index 1a3a710e0be..20909522859 100644 --- a/src/java.base/share/classes/java/util/spi/ToolProvider.java +++ b/src/java.base/share/classes/java/util/spi/ToolProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -27,8 +27,6 @@ package java.util.spi; import java.io.PrintStream; import java.io.PrintWriter; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.Objects; import java.util.Optional; import java.util.ServiceLoader; @@ -178,18 +176,14 @@ public interface ToolProvider { * * @throws NullPointerException if {@code name} is {@code null} */ - @SuppressWarnings("removal") static Optional findFirst(String name) { Objects.requireNonNull(name); ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); - return AccessController.doPrivileged( - (PrivilegedAction>) () -> { - ServiceLoader sl = - ServiceLoader.load(ToolProvider.class, systemClassLoader); - return StreamSupport.stream(sl.spliterator(), false) - .filter(p -> p.name().equals(name)) - .findFirst(); - }); + + ServiceLoader sl = + ServiceLoader.load(ToolProvider.class, systemClassLoader); + return StreamSupport.stream(sl.spliterator(), false) + .filter(p -> p.name().equals(name)) + .findFirst(); } } - diff --git a/src/java.base/share/classes/java/util/stream/Tripwire.java b/src/java.base/share/classes/java/util/stream/Tripwire.java index 962c7d3e1b0..6aae5e67e50 100644 --- a/src/java.base/share/classes/java/util/stream/Tripwire.java +++ b/src/java.base/share/classes/java/util/stream/Tripwire.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -24,9 +24,6 @@ */ package java.util.stream; -import java.security.AccessController; -import java.security.PrivilegedAction; - import sun.util.logging.PlatformLogger; /** @@ -49,9 +46,7 @@ final class Tripwire { private static final String TRIPWIRE_PROPERTY = "org.openjdk.java.util.stream.tripwire"; /** Should debugging checks be enabled? */ - @SuppressWarnings("removal") - static final boolean ENABLED = AccessController.doPrivileged( - (PrivilegedAction) () -> Boolean.getBoolean(TRIPWIRE_PROPERTY)); + static final boolean ENABLED = Boolean.getBoolean(TRIPWIRE_PROPERTY); private Tripwire() { } diff --git a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java index ec6b7591282..e69690f876f 100644 --- a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java +++ b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java @@ -57,6 +57,11 @@ public final class Unsafe { private static native void registerNatives(); static { + runtimeSetup(); + } + + // Called from JVM when loading an AOT cache + private static void runtimeSetup() { registerNatives(); } diff --git a/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java b/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java index a5ad79eb7c6..c87c039c133 100644 --- a/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java +++ b/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -370,14 +370,6 @@ class ModuleReferences { ExplodedModuleReader(Path dir) { this.dir = dir; - - // when running with a security manager then check that the caller - // has access to the directory. - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - boolean unused = Files.isDirectory(dir); - } } /** diff --git a/src/java.base/share/classes/jdk/internal/module/Modules.java b/src/java.base/share/classes/jdk/internal/module/Modules.java index 11ca2d5e521..3c3d148e196 100644 --- a/src/java.base/share/classes/jdk/internal/module/Modules.java +++ b/src/java.base/share/classes/jdk/internal/module/Modules.java @@ -32,8 +32,6 @@ import java.lang.module.ModuleFinder; import java.lang.module.ModuleReference; import java.lang.module.ResolvedModule; import java.net.URI; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.Collection; import java.util.List; import java.util.Map; @@ -155,10 +153,7 @@ public class Modules { public static void addProvides(Module m, Class service, Class impl) { ModuleLayer layer = m.getLayer(); - PrivilegedAction pa = m::getClassLoader; - @SuppressWarnings("removal") - ClassLoader loader = AccessController.doPrivileged(pa); - + ClassLoader loader = m.getClassLoader(); ClassLoader platformClassLoader = ClassLoaders.platformClassLoader(); if (layer == null || loader == null || loader == platformClassLoader) { // update ClassLoader catalog diff --git a/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java b/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java index 74af7570d57..c520e6e636a 100644 --- a/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java +++ b/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -38,8 +38,6 @@ import java.net.URLConnection; import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.ArrayDeque; import java.util.Collections; import java.util.Deque; @@ -208,21 +206,7 @@ public final class SystemModuleFinders { Path dir = Path.of(home, "modules"); if (!Files.isDirectory(dir)) throw new InternalError("Unable to detect the run-time image"); - ModuleFinder f = ModulePath.of(ModuleBootstrap.patcher(), dir); - return new ModuleFinder() { - @SuppressWarnings("removal") - @Override - public Optional find(String name) { - PrivilegedAction> pa = () -> f.find(name); - return AccessController.doPrivileged(pa); - } - @SuppressWarnings("removal") - @Override - public Set findAll() { - PrivilegedAction> pa = f::findAll; - return AccessController.doPrivileged(pa); - } - }; + return ModulePath.of(ModuleBootstrap.patcher(), dir); } /** @@ -314,7 +298,7 @@ public final class SystemModuleFinders { Supplier readerSupplier = new Supplier<>() { @Override public ModuleReader get() { - return new SystemModuleReader(mn, uri); + return new SystemModuleReader(mn); } }; @@ -377,9 +361,7 @@ public final class SystemModuleFinders { } /** - * Holder class for the ImageReader - * - * @apiNote This class must be loaded before a security manager is set. + * Holder class for the ImageReader. */ private static class SystemImage { static final ImageReader READER = ImageReaderFactory.getImageReader(); @@ -396,25 +378,7 @@ public final class SystemModuleFinders { private final String module; private volatile boolean closed; - /** - * If there is a security manager set then check permission to - * connect to the run-time image. - */ - private static void checkPermissionToConnect(URI uri) { - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - try { - URLConnection uc = uri.toURL().openConnection(); - sm.checkPermission(uc.getPermission()); - } catch (IOException ioe) { - throw new UncheckedIOException(ioe); - } - } - } - - SystemModuleReader(String module, URI uri) { - checkPermissionToConnect(uri); + SystemModuleReader(String module) { this.module = module; } diff --git a/src/java.base/share/classes/jdk/internal/util/ClassFileDumper.java b/src/java.base/share/classes/jdk/internal/util/ClassFileDumper.java index b104b56ac0e..83dafb6b49c 100644 --- a/src/java.base/share/classes/jdk/internal/util/ClassFileDumper.java +++ b/src/java.base/share/classes/jdk/internal/util/ClassFileDumper.java @@ -29,8 +29,6 @@ import jdk.internal.misc.VM; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.HexFormat; import java.util.Objects; import java.util.Set; @@ -79,10 +77,6 @@ public final class ClassFileDumper { private final AtomicInteger counter = new AtomicInteger(); private ClassFileDumper(String key, String path) { - /* - * GetPropertyAction.privilegedGetProperty cannot be used here, Using VM.getSavedProperty to avoid a bootstrap - * circularity issue in the java/lang/String/concat/WithSecurityManager.java test - */ String value = VM.getSavedProperty(key); this.key = key; boolean enabled = value != null && value.isEmpty() ? true : Boolean.parseBoolean(value); @@ -132,50 +126,39 @@ public final class ClassFileDumper { write(pathname(name + ".failed-" + counter.incrementAndGet()), bytes); } - @SuppressWarnings("removal") private void write(Path path, byte[] bytes) { - AccessController.doPrivileged(new PrivilegedAction<>() { - @Override public Void run() { - try { - Files.createDirectories(path.getParent()); - Files.write(path, bytes); - } catch (Exception ex) { - if (VM.isModuleSystemInited()) { - // log only when lambda is ready to use - System.getLogger(ClassFileDumper.class.getName()) - .log(System.Logger.Level.WARNING, "Exception writing to " + - path + " " + ex.getMessage()); - } - // simply don't care if this operation failed - } - return null; - }}); + try { + Files.createDirectories(path.getParent()); + Files.write(path, bytes); + } catch (Exception ex) { + if (VM.isModuleSystemInited()) { + // log only when lambda is ready to use + System.getLogger(ClassFileDumper.class.getName()) + .log(System.Logger.Level.WARNING, "Exception writing to " + + path + " " + ex.getMessage()); + } + // simply don't care if this operation failed + } } /* * Validate if the given dir is a writeable directory if exists. */ - @SuppressWarnings("removal") private static Path validateDumpDir(String dir) { - return AccessController.doPrivileged(new PrivilegedAction<>() { - @Override - public Path run() { - Path path = Path.of(dir); - if (Files.notExists(path)) { - try { - Files.createDirectories(path); - } catch (IOException ex) { - throw new IllegalArgumentException("Fail to create " + path, ex); - } - } - if (!Files.isDirectory(path)) { - throw new IllegalArgumentException("Path " + path + " is not a directory"); - } else if (!Files.isWritable(path)) { - throw new IllegalArgumentException("Directory " + path + " is not writable"); - } - return path; + Path path = Path.of(dir); + if (Files.notExists(path)) { + try { + Files.createDirectories(path); + } catch (IOException ex) { + throw new IllegalArgumentException("Fail to create " + path, ex); } - }); + } + if (!Files.isDirectory(path)) { + throw new IllegalArgumentException("Path " + path + " is not a directory"); + } else if (!Files.isWritable(path)) { + throw new IllegalArgumentException("Directory " + path + " is not writable"); + } + return path; } private static final HexFormat HEX = HexFormat.of().withUpperCase(); diff --git a/src/java.base/share/classes/jdk/internal/util/StaticProperty.java b/src/java.base/share/classes/jdk/internal/util/StaticProperty.java index 9dcebeca470..abe2dafa3b7 100644 --- a/src/java.base/share/classes/jdk/internal/util/StaticProperty.java +++ b/src/java.base/share/classes/jdk/internal/util/StaticProperty.java @@ -32,14 +32,11 @@ import java.util.Properties; * Read-only access to System property values initialized during Phase 1 * are cached. Setting, clearing, or modifying the value using * {@link System#setProperty} or {@link System#getProperties()} is ignored. - * {@link SecurityManager#checkPropertyAccess} is NOT checked - * in these access methods. The caller of these methods should take care to ensure - * that the returned property is not made accessible to untrusted code. */ public final class StaticProperty { // The class static initialization is triggered to initialize these final - // fields during init Phase 1 and before a security manager is set. + // fields during init Phase 1. private static final String JAVA_HOME; private static final String USER_HOME; private static final String USER_DIR; @@ -143,10 +140,6 @@ public final class StaticProperty { /** * {@return the {@code java.home} system property} - * - * {@link SecurityManager#checkPropertyAccess} is NOT checked - * in this method. The caller of this method should take care to ensure - * that the returned property is not made accessible to untrusted code. */ public static String javaHome() { return JAVA_HOME; @@ -154,10 +147,6 @@ public final class StaticProperty { /** * {@return the {@code user.home} system property} - * - * {@link SecurityManager#checkPropertyAccess} is NOT checked - * in this method. The caller of this method should take care to ensure - * that the returned property is not made accessible to untrusted code. */ public static String userHome() { return USER_HOME; @@ -165,10 +154,6 @@ public final class StaticProperty { /** * {@return the {@code user.dir} system property} - * - * {@link SecurityManager#checkPropertyAccess} is NOT checked - * in this method. The caller of this method should take care to ensure - * that the returned property is not made accessible to untrusted code. */ public static String userDir() { return USER_DIR; @@ -176,10 +161,6 @@ public final class StaticProperty { /** * {@return the {@code user.name} system property} - * - * {@link SecurityManager#checkPropertyAccess} is NOT checked - * in this method. The caller of this method should take care to ensure - * that the returned property is not made accessible to untrusted code. */ public static String userName() { return USER_NAME; @@ -187,10 +168,6 @@ public final class StaticProperty { /** * {@return the {@code java.library.path} system property} - * - * {@link SecurityManager#checkPropertyAccess} is NOT checked - * in this method. The caller of this method should take care to ensure - * that the returned property is not made accessible to untrusted code. */ public static String javaLibraryPath() { return JAVA_LIBRARY_PATH; @@ -198,10 +175,6 @@ public final class StaticProperty { /** * {@return the {@code java.io.tmpdir} system property} - * - * {@link SecurityManager#checkPropertyAccess} is NOT checked - * in this method. The caller of this method should take care to ensure - * that the returned property is not made accessible to untrusted code. */ public static String javaIoTmpDir() { return JAVA_IO_TMPDIR; @@ -209,10 +182,6 @@ public final class StaticProperty { /** * {@return the {@code sun.boot.library.path} system property} - * - * {@link SecurityManager#checkPropertyAccess} is NOT checked - * in this method. The caller of this method should take care to ensure - * that the returned property is not made accessible to untrusted code. */ public static String sunBootLibraryPath() { return SUN_BOOT_LIBRARY_PATH; @@ -221,10 +190,6 @@ public final class StaticProperty { /** * {@return the {@code jdk.serialFilter} system property} - * - * {@link SecurityManager#checkPropertyAccess} is NOT checked - * in this method. The caller of this method should take care to ensure - * that the returned property is not made accessible to untrusted code. */ public static String jdkSerialFilter() { return JDK_SERIAL_FILTER; @@ -233,10 +198,6 @@ public final class StaticProperty { /** * {@return the {@code jdk.serialFilterFactory} system property} - * - * {@link SecurityManager#checkPropertyAccess} is NOT checked - * in this method. The caller of this method should take care to ensure - * that the returned property is not made accessible to untrusted code. */ public static String jdkSerialFilterFactory() { return JDK_SERIAL_FILTER_FACTORY; @@ -244,10 +205,6 @@ public final class StaticProperty { /** * {@return the {@code native.encoding} system property} - * - * {@link SecurityManager#checkPropertyAccess} is NOT checked - * in this method. The caller of this method should take care to ensure - * that the returned property is not made accessible to untrusted code. */ public static String nativeEncoding() { return NATIVE_ENCODING; @@ -255,10 +212,6 @@ public final class StaticProperty { /** * {@return the {@code file.encoding} system property} - * - * {@link SecurityManager#checkPropertyAccess} is NOT checked - * in this method. The caller of this method should take care to ensure - * that the returned property is not made accessible to untrusted code. */ public static String fileEncoding() { return FILE_ENCODING; @@ -266,9 +219,6 @@ public final class StaticProperty { /** * {@return the {@code java.properties.date} system property} - * - * {@link SecurityManager#checkPropertyAccess} is NOT checked - * in this method. */ public static String javaPropertiesDate() { return JAVA_PROPERTIES_DATE; @@ -276,10 +226,6 @@ public final class StaticProperty { /** * {@return the {@code sun.jnu.encoding} system property} - * - * {@link SecurityManager#checkPropertyAccess} is NOT checked - * in this method. The caller of this method should take care to ensure - * that the returned property is not made accessible to untrusted code. */ public static String jnuEncoding() { return SUN_JNU_ENCODING; @@ -287,10 +233,6 @@ public final class StaticProperty { /** * {@return the {@code java.locale.useOldISOCodes} system property} - * - * {@link SecurityManager#checkPropertyAccess} is NOT checked - * in this method. The caller of this method should take care to ensure - * that the returned property is not made accessible to untrusted code. */ public static String javaLocaleUseOldISOCodes() { return JAVA_LOCALE_USE_OLD_ISO_CODES; @@ -298,8 +240,6 @@ public final class StaticProperty { /** * {@return the {@code os.name} system property} - * {@link SecurityManager#checkPropertyAccess} is NOT checked - * in this method. This property is not considered security sensitive. */ public static String osName() { return OS_NAME; @@ -307,8 +247,6 @@ public final class StaticProperty { /** * {@return the {@code os.arch} system property} - * {@link SecurityManager#checkPropertyAccess} is NOT checked - * in this method. This property is not considered security sensitive. */ public static String osArch() { return OS_ARCH; @@ -316,8 +254,6 @@ public final class StaticProperty { /** * {@return the {@code os.version} system property} - * {@link SecurityManager#checkPropertyAccess} is NOT checked - * in this method. This property is not considered security sensitive. */ public static String osVersion() { return OS_VERSION; diff --git a/src/java.base/share/classes/jdk/internal/util/random/RandomSupport.java b/src/java.base/share/classes/jdk/internal/util/random/RandomSupport.java index 402be6b5dfe..71c4c8fbbbb 100644 --- a/src/java.base/share/classes/jdk/internal/util/random/RandomSupport.java +++ b/src/java.base/share/classes/jdk/internal/util/random/RandomSupport.java @@ -725,11 +725,7 @@ public class RandomSupport { // The following decides which of two strategies initialSeed() will use. private static boolean secureRandomSeedRequested() { - @SuppressWarnings("removal") - String pp = java.security.AccessController.doPrivileged( - new sun.security.action.GetPropertyAction( - "java.util.secureRandomSeed")); - return (pp != null && pp.equalsIgnoreCase("true")); + return Boolean.getBoolean("java.util.secureRandomSeed"); } private static final boolean useSecureRandomSeed = secureRandomSeedRequested(); diff --git a/src/java.base/share/classes/sun/net/smtp/SmtpClient.java b/src/java.base/share/classes/sun/net/smtp/SmtpClient.java index 59c60ce70dc..494bcf19987 100644 --- a/src/java.base/share/classes/sun/net/smtp/SmtpClient.java +++ b/src/java.base/share/classes/sun/net/smtp/SmtpClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 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 @@ -28,7 +28,6 @@ package sun.net.smtp; import java.io.*; import java.net.*; import sun.net.TransferProtocolClient; -import sun.security.action.GetPropertyAction; /** * This class implements the SMTP client. @@ -167,7 +166,7 @@ public class SmtpClient extends TransferProtocolClient { } try { String s; - mailhost = GetPropertyAction.privilegedGetProperty("mail.host"); + mailhost = System.getProperty("mail.host"); if (mailhost != null) { openServer(mailhost); return; @@ -193,7 +192,7 @@ public class SmtpClient extends TransferProtocolClient { setConnectTimeout(to); try { String s; - mailhost = GetPropertyAction.privilegedGetProperty("mail.host"); + mailhost = System.getProperty("mail.host"); if (mailhost != null) { openServer(mailhost); return; diff --git a/src/java.base/share/classes/sun/net/spi/DefaultProxySelector.java b/src/java.base/share/classes/sun/net/spi/DefaultProxySelector.java index c944bef91e7..763b176331e 100644 --- a/src/java.base/share/classes/sun/net/spi/DefaultProxySelector.java +++ b/src/java.base/share/classes/sun/net/spi/DefaultProxySelector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -33,8 +33,6 @@ import java.net.URI; import java.util.Collections; import java.util.List; import java.io.IOException; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.Locale; import java.util.StringJoiner; import java.util.regex.Pattern; @@ -93,26 +91,15 @@ public class DefaultProxySelector extends ProxySelector { static { final String key = "java.net.useSystemProxies"; - @SuppressWarnings("removal") - Boolean b = AccessController.doPrivileged( - new PrivilegedAction() { - public Boolean run() { - return NetProperties.getBoolean(key); - }}); + Boolean b = NetProperties.getBoolean(key); if (b != null && b.booleanValue()) { jdk.internal.loader.BootLoader.loadLibrary("net"); hasSystemProxies = init(); } } - @SuppressWarnings("removal") public static int socksProxyVersion() { - return AccessController.doPrivileged( - new PrivilegedAction() { - @Override public Integer run() { - return NetProperties.getInteger(SOCKS_PROXY_VERSION, 5); - } - }); + return NetProperties.getInteger(SOCKS_PROXY_VERSION, 5); } /** @@ -187,148 +174,133 @@ public class DefaultProxySelector extends ProxySelector { throw new IllegalArgumentException("protocol = "+protocol+" host = "+host); } - NonProxyInfo pinfo = null; + NonProxyInfo nonProxyInfo = null; if ("http".equalsIgnoreCase(protocol)) { - pinfo = NonProxyInfo.httpNonProxyInfo; + nonProxyInfo = NonProxyInfo.httpNonProxyInfo; } else if ("https".equalsIgnoreCase(protocol)) { // HTTPS uses the same property as HTTP, for backward // compatibility - pinfo = NonProxyInfo.httpNonProxyInfo; + nonProxyInfo = NonProxyInfo.httpNonProxyInfo; } else if ("ftp".equalsIgnoreCase(protocol)) { - pinfo = NonProxyInfo.ftpNonProxyInfo; + nonProxyInfo = NonProxyInfo.ftpNonProxyInfo; } else if ("socket".equalsIgnoreCase(protocol)) { - pinfo = NonProxyInfo.socksNonProxyInfo; + nonProxyInfo = NonProxyInfo.socksNonProxyInfo; } - - /** - * Let's check the System properties for that protocol - */ - final String proto = protocol; - final NonProxyInfo nprop = pinfo; final String urlhost = host.toLowerCase(Locale.ROOT); - - /** - * This is one big doPrivileged call, but we're trying to optimize - * the code as much as possible. Since we're checking quite a few - * System properties it does help having only 1 call to doPrivileged. - * Be mindful what you do in here though! - */ - @SuppressWarnings("removal") - Proxy[] proxyArray = AccessController.doPrivileged( - new PrivilegedAction() { - public Proxy[] run() { - int i, j; - String phost = null; - int pport = 0; - String nphosts = null; - InetSocketAddress saddr = null; - - // Then let's walk the list of protocols in our array - for (i=0; i extensionMap = new Hashtable<>(); // Will be reset if in the platform-specific data file - @SuppressWarnings("removal") private static String tempFileTemplate = - java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction() { - public String run() { - return System.getProperty("content.types.temp.file.template", - "/tmp/%s"); - } - }); + System.getProperty("content.types.temp.file.template", "/tmp/%s"); private static final String filePreamble = "sun.net.www MIME content-types table"; @@ -70,16 +63,10 @@ public class MimeTable implements FileNameMap { private static class DefaultInstanceHolder { static final MimeTable defaultInstance = getDefaultInstance(); - @SuppressWarnings("removal") static MimeTable getDefaultInstance() { - return java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction<>() { - public MimeTable run() { - MimeTable instance = new MimeTable(); - URLConnection.setFileNameMap(instance); - return instance; - } - }); + final MimeTable instance = new MimeTable(); + URLConnection.setFileNameMap(instance); + return instance; } } @@ -235,20 +222,14 @@ public class MimeTable implements FileNameMap { // For backward compatibility -- mailcap format files // This is not currently used, but may in the future when we add ability // to read BOTH the properties format and the mailcap format. - @SuppressWarnings("removal") protected static String[] mailcapLocations = - java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction() { - public String[] run() { - return new String[]{ - System.getProperty("user.mailcap"), - StaticProperty.userHome() + "/.mailcap", - "/etc/mailcap", - "/usr/etc/mailcap", - "/usr/local/etc/mailcap", - }; - } - }); + new String[]{ + System.getProperty("user.mailcap"), + StaticProperty.userHome() + "/.mailcap", + "/etc/mailcap", + "/usr/etc/mailcap", + "/usr/local/etc/mailcap" + }; public synchronized void load() { Properties entries = new Properties(); @@ -402,12 +383,6 @@ public class MimeTable implements FileNameMap { Properties properties = getAsProperties(); properties.put("temp.file.template", tempFileTemplate); String tag; - // Perform the property security check for user.name - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - sm.checkPropertyAccess("user.name"); - } String user = StaticProperty.userName(); if (user != null) { tag = "; customized for " + user; diff --git a/src/java.base/share/classes/sun/net/www/protocol/jrt/JavaRuntimeURLConnection.java b/src/java.base/share/classes/sun/net/www/protocol/jrt/JavaRuntimeURLConnection.java index b4cd98870ca..4e67c962a46 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/jrt/JavaRuntimeURLConnection.java +++ b/src/java.base/share/classes/sun/net/www/protocol/jrt/JavaRuntimeURLConnection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -30,15 +30,11 @@ import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; -import java.security.AccessController; -import java.security.Permission; -import java.security.PrivilegedAction; import jdk.internal.jimage.ImageLocation; import jdk.internal.jimage.ImageReader; import jdk.internal.jimage.ImageReaderFactory; -import jdk.internal.loader.URLClassPath; import jdk.internal.loader.Resource; import sun.net.www.ParseUtil; import sun.net.www.URLConnection; @@ -47,15 +43,10 @@ import sun.net.www.URLConnection; * URLConnection implementation that can be used to connect to resources * contained in the runtime image. */ -@SuppressWarnings("removal") public class JavaRuntimeURLConnection extends URLConnection { // ImageReader to access resources in jimage - private static final ImageReader reader; - static { - PrivilegedAction pa = ImageReaderFactory::getImageReader; - reader = AccessController.doPrivileged(pa); - } + private static final ImageReader reader = ImageReaderFactory.getImageReader(); // the module and resource name in the URL private final String module; @@ -92,7 +83,7 @@ public class JavaRuntimeURLConnection extends URLConnection { if (reader != null) { URL url = toJrtURL(module, name); ImageLocation location = reader.findLocation(module, name); - if (location != null && URLClassPath.checkURL(url) != null) { + if (location != null) { return new Resource() { @Override public String getName() { @@ -158,11 +149,6 @@ public class JavaRuntimeURLConnection extends URLConnection { return len > Integer.MAX_VALUE ? -1 : (int)len; } - @Override - public Permission getPermission() { - return new RuntimePermission("accessSystemModules"); - } - /** * Returns a jrt URL for the given module and resource name. */ diff --git a/src/java.base/share/classes/sun/net/www/protocol/mailto/MailToURLConnection.java b/src/java.base/share/classes/sun/net/www/protocol/mailto/MailToURLConnection.java index 555279f71ee..7791d07a8ca 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/mailto/MailToURLConnection.java +++ b/src/java.base/share/classes/sun/net/www/protocol/mailto/MailToURLConnection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 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 @@ -27,9 +27,7 @@ package sun.net.www.protocol.mailto; import java.net.URL; import java.net.InetAddress; -import java.net.SocketPermission; import java.io.*; -import java.security.Permission; import jdk.internal.util.StaticProperty; import sun.net.www.*; @@ -48,7 +46,6 @@ public class MailToURLConnection extends URLConnection { OutputStream os = null; SmtpClient client; - Permission permission; private int connectTimeout = -1; private int readTimeout = -1; @@ -67,12 +64,6 @@ public class MailToURLConnection extends URLConnection { String getFromAddress() { String str = System.getProperty("user.fromaddr"); if (str == null) { - // Perform the property security check for user.name - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - sm.checkPropertyAccess("user.name"); - } str = StaticProperty.userName(); if (str != null) { String host = System.getProperty("mail.host"); @@ -112,16 +103,6 @@ public class MailToURLConnection extends URLConnection { return os; } - @Override - public Permission getPermission() throws IOException { - if (permission == null) { - connect(); - String host = client.getMailHost() + ":" + 25; - permission = new SocketPermission(host, "connect"); - } - return permission; - } - @Override public void setConnectTimeout(int timeout) { if (timeout < 0) diff --git a/src/java.base/share/classes/sun/security/util/SecurityConstants.java b/src/java.base/share/classes/sun/security/util/SecurityConstants.java index df2861f3034..21ec592c766 100644 --- a/src/java.base/share/classes/sun/security/util/SecurityConstants.java +++ b/src/java.base/share/classes/sun/security/util/SecurityConstants.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -72,30 +72,6 @@ public final class SecurityConstants { public static final NetPermission SPECIFY_HANDLER_PERMISSION = new NetPermission("specifyStreamHandler"); - // java.net.ProxySelector - public static final NetPermission SET_PROXYSELECTOR_PERMISSION = - new NetPermission("setProxySelector"); - - // java.net.ProxySelector - public static final NetPermission GET_PROXYSELECTOR_PERMISSION = - new NetPermission("getProxySelector"); - - // java.net.CookieHandler - public static final NetPermission SET_COOKIEHANDLER_PERMISSION = - new NetPermission("setCookieHandler"); - - // java.net.CookieHandler - public static final NetPermission GET_COOKIEHANDLER_PERMISSION = - new NetPermission("getCookieHandler"); - - // java.net.ResponseCache - public static final NetPermission SET_RESPONSECACHE_PERMISSION = - new NetPermission("setResponseCache"); - - // java.net.ResponseCache - public static final NetPermission GET_RESPONSECACHE_PERMISSION = - new NetPermission("getResponseCache"); - // java.net.ServerSocket, java.net.Socket public static final NetPermission SET_SOCKETIMPL_PERMISSION = new NetPermission("setSocketImpl"); diff --git a/src/java.desktop/share/classes/java/awt/Component.java b/src/java.desktop/share/classes/java/awt/Component.java index ad0b87fd3fc..a1f8461c802 100644 --- a/src/java.desktop/share/classes/java/awt/Component.java +++ b/src/java.desktop/share/classes/java/awt/Component.java @@ -627,14 +627,10 @@ public abstract class Component implements ImageObserver, MenuContainer, initIDs(); } - @SuppressWarnings("removal") - String s = java.security.AccessController.doPrivileged( - new GetPropertyAction("awt.image.incrementaldraw")); + String s = System.getProperty("awt.image.incrementaldraw"); isInc = (s == null || s.equals("true")); - @SuppressWarnings("removal") - String s2 = java.security.AccessController.doPrivileged( - new GetPropertyAction("awt.image.redrawrate")); + String s2 = System.getProperty("awt.image.redrawrate"); incRate = (s2 != null) ? Integer.parseInt(s2) : 100; } @@ -1431,15 +1427,7 @@ public abstract class Component implements ImageObserver, MenuContainer, throw new HeadlessException(); } - @SuppressWarnings("removal") - PointerInfo pi = java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction() { - public PointerInfo run() { - return MouseInfo.getPointerInfo(); - } - } - ); - + PointerInfo pi = MouseInfo.getPointerInfo(); synchronized (getTreeLock()) { Component inTheSameWindow = findUnderMouseInWindow(pi); if (!isSameOrAncestorOf(inTheSameWindow, true)) { @@ -6253,14 +6241,7 @@ public abstract class Component implements ImageObserver, MenuContainer, } // Need to check non-bootstraps. - @SuppressWarnings("removal") - Boolean enabled = java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction() { - public Boolean run() { - return isCoalesceEventsOverriden(clazz); - } - } - ); + Boolean enabled = isCoalesceEventsOverriden(clazz); coalesceMap.put(clazz, enabled); return enabled; } diff --git a/src/java.desktop/share/classes/java/awt/Container.java b/src/java.desktop/share/classes/java/awt/Container.java index c8289aa7fcc..2eb2c923de0 100644 --- a/src/java.desktop/share/classes/java/awt/Container.java +++ b/src/java.desktop/share/classes/java/awt/Container.java @@ -1576,10 +1576,8 @@ public class Container extends Component { } // Don't lazy-read because every app uses invalidate() - @SuppressWarnings("removal") - private static final boolean isJavaAwtSmartInvalidate - = AccessController.doPrivileged( - new GetBooleanAction("java.awt.smartInvalidate")); + private static final boolean isJavaAwtSmartInvalidate = + Boolean.getBoolean("java.awt.smartInvalidate"); /** * Invalidates the parent of the container unless the container @@ -2632,14 +2630,7 @@ public class Container extends Component { if (GraphicsEnvironment.isHeadless()) { throw new HeadlessException(); } - @SuppressWarnings("removal") - PointerInfo pi = java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction() { - public PointerInfo run() { - return MouseInfo.getPointerInfo(); - } - } - ); + PointerInfo pi = MouseInfo.getPointerInfo(); synchronized (getTreeLock()) { Component inTheSameWindow = findUnderMouseInWindow(pi); if (isSameOrAncestorOf(inTheSameWindow, allowChildren)) { @@ -4738,33 +4729,17 @@ class LightweightDispatcher implements java.io.Serializable, AWTEventListener { * from other heavyweight containers will generate enter/exit * events in this container */ - @SuppressWarnings("removal") private void startListeningForOtherDrags() { //System.out.println("Adding AWTEventListener"); - java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction() { - public Object run() { - nativeContainer.getToolkit().addAWTEventListener( - LightweightDispatcher.this, - AWTEvent.MOUSE_EVENT_MASK | - AWTEvent.MOUSE_MOTION_EVENT_MASK); - return null; - } - } - ); + nativeContainer.getToolkit().addAWTEventListener( + LightweightDispatcher.this, + AWTEvent.MOUSE_EVENT_MASK | + AWTEvent.MOUSE_MOTION_EVENT_MASK); } - @SuppressWarnings("removal") private void stopListeningForOtherDrags() { //System.out.println("Removing AWTEventListener"); - java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction() { - public Object run() { - nativeContainer.getToolkit().removeAWTEventListener(LightweightDispatcher.this); - return null; - } - } - ); + nativeContainer.getToolkit().removeAWTEventListener(LightweightDispatcher.this); } /* diff --git a/src/java.desktop/share/classes/java/awt/Cursor.java b/src/java.desktop/share/classes/java/awt/Cursor.java index cfccf06723b..8a595f61285 100644 --- a/src/java.desktop/share/classes/java/awt/Cursor.java +++ b/src/java.desktop/share/classes/java/awt/Cursor.java @@ -29,8 +29,6 @@ import java.beans.ConstructorProperties; import java.io.InputStream; import java.io.Serial; import java.security.AccessController; -import java.security.PrivilegedAction; -import java.security.PrivilegedExceptionAction; import java.util.Hashtable; import java.util.Properties; import java.util.StringTokenizer; @@ -332,11 +330,7 @@ public class Cursor implements java.io.Serializable { } final Toolkit toolkit = Toolkit.getDefaultToolkit(); final String file = RESOURCE_PREFIX + fileName; - @SuppressWarnings("removal") - final InputStream in = AccessController.doPrivileged( - (PrivilegedAction) () -> { - return Cursor.class.getResourceAsStream(file); - }); + final InputStream in = Cursor.class.getResourceAsStream(file); try (in) { Image image = toolkit.createImage(in.readAllBytes()); cursor = toolkit.createCustomCursor(image, hotPoint, localized); @@ -428,7 +422,6 @@ public class Cursor implements java.io.Serializable { /* * load the cursor.properties file */ - @SuppressWarnings("removal") private static void loadSystemCustomCursorProperties() throws AWTException { synchronized (systemCustomCursors) { if (systemCustomCursorProperties != null) { @@ -437,14 +430,8 @@ public class Cursor implements java.io.Serializable { systemCustomCursorProperties = new Properties(); try { - AccessController.doPrivileged( - (PrivilegedExceptionAction) () -> { - try (InputStream is = Cursor.class - .getResourceAsStream(PROPERTIES_FILE)) { - systemCustomCursorProperties.load(is); - } - return null; - }); + InputStream is = Cursor.class.getResourceAsStream(PROPERTIES_FILE); + systemCustomCursorProperties.load(is); } catch (Exception e) { systemCustomCursorProperties = null; throw new AWTException("Exception: " + e.getClass() + " " + diff --git a/src/java.desktop/share/classes/java/awt/DefaultKeyboardFocusManager.java b/src/java.desktop/share/classes/java/awt/DefaultKeyboardFocusManager.java index e416e791e8d..3c57ec3a8d9 100644 --- a/src/java.desktop/share/classes/java/awt/DefaultKeyboardFocusManager.java +++ b/src/java.desktop/share/classes/java/awt/DefaultKeyboardFocusManager.java @@ -32,8 +32,6 @@ import java.awt.peer.ComponentPeer; import java.awt.peer.LightweightPeer; import java.io.Serial; import java.lang.ref.WeakReference; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.Iterator; import java.util.LinkedList; import java.util.ListIterator; @@ -86,21 +84,14 @@ public class DefaultKeyboardFocusManager extends KeyboardFocusManager { initStatic(); } - @SuppressWarnings("removal") private static void initStatic() { AWTAccessor.setDefaultKeyboardFocusManagerAccessor( new AWTAccessor.DefaultKeyboardFocusManagerAccessor() { public void consumeNextKeyTyped(DefaultKeyboardFocusManager dkfm, KeyEvent e) { dkfm.consumeNextKeyTyped(e); - } + } }); - AccessController.doPrivileged(new PrivilegedAction() { - public Object run() { - fxAppThreadIsDispatchThread = - "true".equals(System.getProperty("javafx.embed.singleThread")); - return null; - } - }); + fxAppThreadIsDispatchThread = "true".equals(System.getProperty("javafx.embed.singleThread")); } /** diff --git a/src/java.desktop/share/classes/java/awt/Desktop.java b/src/java.desktop/share/classes/java/awt/Desktop.java index 025889755b8..89a78518873 100644 --- a/src/java.desktop/share/classes/java/awt/Desktop.java +++ b/src/java.desktop/share/classes/java/awt/Desktop.java @@ -42,8 +42,6 @@ import java.io.FilePermission; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.Objects; import javax.swing.JMenuBar; @@ -936,11 +934,7 @@ public class Desktop { sm.checkDelete(file.getPath()); } checkActionSupport(Action.MOVE_TO_TRASH); - final File finalFile = file; - AccessController.doPrivileged((PrivilegedAction) () -> { - checkFileValidation(finalFile); - return null; - }); + checkFileValidation(file); return peer.moveToTrash(file); } } diff --git a/src/java.desktop/share/classes/java/awt/Dialog.java b/src/java.desktop/share/classes/java/awt/Dialog.java index a0d22fbb327..03449200478 100644 --- a/src/java.desktop/share/classes/java/awt/Dialog.java +++ b/src/java.desktop/share/classes/java/awt/Dialog.java @@ -34,8 +34,6 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serial; import java.security.AccessControlException; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.Iterator; import java.util.concurrent.atomic.AtomicLong; @@ -1052,9 +1050,7 @@ public class Dialog extends Window { modalityPushed(); try { - @SuppressWarnings("removal") - final EventQueue eventQueue = AccessController.doPrivileged( - (PrivilegedAction) Toolkit.getDefaultToolkit()::getSystemEventQueue); + EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue(); secondaryLoop = eventQueue.createSecondaryLoop(() -> true, modalFilter, 0); if (!secondaryLoop.enter()) { secondaryLoop = null; diff --git a/src/java.desktop/share/classes/java/awt/EventQueue.java b/src/java.desktop/share/classes/java/awt/EventQueue.java index 4dce257f727..57a9a6cf567 100644 --- a/src/java.desktop/share/classes/java/awt/EventQueue.java +++ b/src/java.desktop/share/classes/java/awt/EventQueue.java @@ -32,9 +32,6 @@ import java.awt.peer.ComponentPeer; import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; -import java.security.AccessController; -import java.security.PrivilegedAction; - import java.util.EmptyStackException; import sun.awt.*; @@ -45,11 +42,6 @@ import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.atomic.AtomicInteger; -import java.security.AccessControlContext; - -import jdk.internal.access.SharedSecrets; -import jdk.internal.access.JavaSecurityAccess; - /** * {@code EventQueue} is a platform-independent class * that queues events, both from the underlying peer classes @@ -229,13 +221,8 @@ public class EventQueue { }); } - @SuppressWarnings("removal") private static boolean fxAppThreadIsDispatchThread = - AccessController.doPrivileged(new PrivilegedAction() { - public Boolean run() { - return "true".equals(System.getProperty("javafx.embed.singleThread")); - } - }); + "true".equals(System.getProperty("javafx.embed.singleThread")); /** * Initializes a new instance of {@code EventQueue}. @@ -668,9 +655,6 @@ public class EventQueue { return null; } - private static final JavaSecurityAccess javaSecurityAccess = - SharedSecrets.getJavaSecurityAccess(); - /** * Dispatches an event. The manner in which the event is * dispatched depends upon the type of the event and the @@ -711,59 +695,25 @@ public class EventQueue { */ protected void dispatchEvent(final AWTEvent event) { final Object src = event.getSource(); - final PrivilegedAction action = new PrivilegedAction() { - public Void run() { - // In case fwDispatcher is installed and we're already on the - // dispatch thread (e.g. performing DefaultKeyboardFocusManager.sendMessage), - // dispatch the event straight away. - if (fwDispatcher == null || isDispatchThreadImpl()) { - dispatchEventImpl(event, src); - } else { - fwDispatcher.scheduleDispatch(new Runnable() { - @Override - public void run() { - if (dispatchThread.filterAndCheckEvent(event)) { - dispatchEventImpl(event, src); - } - } - }); - } - return null; - } - }; - - @SuppressWarnings("removal") - final AccessControlContext stack = AccessController.getContext(); - @SuppressWarnings("removal") - final AccessControlContext srcAcc = getAccessControlContextFrom(src); - @SuppressWarnings("removal") - final AccessControlContext eventAcc = event.getAccessControlContext(); - if (srcAcc == null) { - javaSecurityAccess.doIntersectionPrivilege(action, stack, eventAcc); + // In case fwDispatcher is installed and we're already on the + // dispatch thread (e.g. performing DefaultKeyboardFocusManager.sendMessage), + // dispatch the event straight away. + if (fwDispatcher == null || isDispatchThreadImpl()) { + dispatchEventImpl(event, src); } else { - javaSecurityAccess.doIntersectionPrivilege( - new PrivilegedAction() { - public Void run() { - javaSecurityAccess.doIntersectionPrivilege(action, eventAcc); - return null; + fwDispatcher.scheduleDispatch(new Runnable() { + @Override + public void run() { + if (dispatchThread.filterAndCheckEvent(event)) { + dispatchEventImpl(event, src); } - }, stack, srcAcc); + } + }); } } - @SuppressWarnings("removal") - private static AccessControlContext getAccessControlContextFrom(Object src) { - return src instanceof Component ? - ((Component)src).getAccessControlContext() : - src instanceof MenuComponent ? - ((MenuComponent)src).getAccessControlContext() : - src instanceof TrayIcon ? - ((TrayIcon)src).getAccessControlContext() : - null; - } - /** - * Called from dispatchEvent() under a correct AccessControlContext + * Called from dispatchEvent() */ private void dispatchEventImpl(final AWTEvent event, final Object src) { event.isPosted = true; @@ -1113,21 +1063,12 @@ public class EventQueue { pushPopLock.lock(); try { if (dispatchThread == null && !threadGroup.isDestroyed() && !appContext.isDisposed()) { - dispatchThread = AccessController.doPrivileged( - new PrivilegedAction() { - public EventDispatchThread run() { - EventDispatchThread t = - new EventDispatchThread(threadGroup, - name, - EventQueue.this); - t.setContextClassLoader(classLoader); - t.setPriority(Thread.NORM_PRIORITY + 1); - t.setDaemon(false); - AWTAutoShutdown.getInstance().notifyThreadBusy(t); - return t; - } - } - ); + EventDispatchThread t = new EventDispatchThread(threadGroup, name, EventQueue.this); + t.setContextClassLoader(classLoader); + t.setPriority(Thread.NORM_PRIORITY + 1); + t.setDaemon(false); + AWTAutoShutdown.getInstance().notifyThreadBusy(t); + dispatchThread = t; dispatchThread.start(); } } finally { diff --git a/src/java.desktop/share/classes/java/awt/Font.java b/src/java.desktop/share/classes/java/awt/Font.java index ff5c29a2758..3df4632f9d3 100644 --- a/src/java.desktop/share/classes/java/awt/Font.java +++ b/src/java.desktop/share/classes/java/awt/Font.java @@ -44,8 +44,6 @@ import java.io.OutputStream; import java.io.Serial; import java.lang.ref.SoftReference; import java.nio.file.Files; -import java.security.AccessController; -import java.security.PrivilegedExceptionAction; import java.text.AttributedCharacterIterator.Attribute; import java.text.CharacterIterator; import java.util.EventListener; @@ -1095,7 +1093,6 @@ public class Font implements java.io.Serializable } } - @SuppressWarnings("removal") private static Font[] createFont0(int fontFormat, InputStream fontStream, boolean allFonts, CreatedFontTracker tracker) @@ -1107,27 +1104,14 @@ public class Font implements java.io.Serializable } boolean copiedFontData = false; try { - final File tFile = AccessController.doPrivileged( - new PrivilegedExceptionAction() { - public File run() throws IOException { - return Files.createTempFile("+~JF", ".tmp").toFile(); - } - } - ); + final File tFile = Files.createTempFile("+~JF", ".tmp").toFile(); if (tracker != null) { tracker.add(tFile); } int totalSize = 0; try { - final OutputStream outStream = - AccessController.doPrivileged( - new PrivilegedExceptionAction() { - public OutputStream run() throws IOException { - return new FileOutputStream(tFile); - } - } - ); + final OutputStream outStream = new FileOutputStream(tFile); if (tracker != null) { tracker.set(tFile, outStream); } @@ -1181,14 +1165,7 @@ public class Font implements java.io.Serializable if (tracker != null) { tracker.subBytes(totalSize); } - AccessController.doPrivileged( - new PrivilegedExceptionAction() { - public Void run() { - tFile.delete(); - return null; - } - } - ); + tFile.delete(); } } } catch (Throwable t) { diff --git a/src/java.desktop/share/classes/java/awt/GraphicsEnvironment.java b/src/java.desktop/share/classes/java/awt/GraphicsEnvironment.java index 2e89aa6f672..4936d5d396d 100644 --- a/src/java.desktop/share/classes/java/awt/GraphicsEnvironment.java +++ b/src/java.desktop/share/classes/java/awt/GraphicsEnvironment.java @@ -26,8 +26,6 @@ package java.awt; import java.awt.image.BufferedImage; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.Locale; import sun.awt.PlatformGraphicsInfo; @@ -137,20 +135,16 @@ public abstract class GraphicsEnvironment { * @return the value of the property "java.awt.headless" * @since 1.4 */ - @SuppressWarnings("removal") private static boolean getHeadlessProperty() { if (headless == null) { - AccessController.doPrivileged((PrivilegedAction) () -> { - String nm = System.getProperty("java.awt.headless"); + String nm = System.getProperty("java.awt.headless"); - if (nm == null) { - headless = defaultHeadless = - PlatformGraphicsInfo.getDefaultHeadlessProperty(); - } else { - headless = Boolean.valueOf(nm); - } - return null; - }); + if (nm == null) { + headless = defaultHeadless = + PlatformGraphicsInfo.getDefaultHeadlessProperty(); + } else { + headless = Boolean.valueOf(nm); + } } return headless; } diff --git a/src/java.desktop/share/classes/java/awt/KeyboardFocusManager.java b/src/java.desktop/share/classes/java/awt/KeyboardFocusManager.java index 0ca86f3433b..eb623213a77 100644 --- a/src/java.desktop/share/classes/java/awt/KeyboardFocusManager.java +++ b/src/java.desktop/share/classes/java/awt/KeyboardFocusManager.java @@ -40,9 +40,6 @@ import java.beans.VetoableChangeSupport; import java.lang.ref.WeakReference; -import java.security.AccessController; -import java.security.PrivilegedAction; - import java.util.Collections; import java.util.HashSet; import java.util.Iterator; @@ -601,14 +598,8 @@ public abstract class KeyboardFocusManager peer.clearGlobalFocusOwner(activeWindow); } - @SuppressWarnings("removal") void clearGlobalFocusOwnerPriv() { - AccessController.doPrivileged(new PrivilegedAction() { - public Void run() { - clearGlobalFocusOwner(); - return null; - } - }); + clearGlobalFocusOwner(); } Component getNativeFocusOwner() { @@ -1194,14 +1185,8 @@ public abstract class KeyboardFocusManager newFocusCycleRoot); } - @SuppressWarnings("removal") void setGlobalCurrentFocusCycleRootPriv(final Container newFocusCycleRoot) { - AccessController.doPrivileged(new PrivilegedAction() { - public Void run() { - setGlobalCurrentFocusCycleRoot(newFocusCycleRoot); - return null; - } - }); + setGlobalCurrentFocusCycleRoot(newFocusCycleRoot); } /** diff --git a/src/java.desktop/share/classes/java/awt/SequencedEvent.java b/src/java.desktop/share/classes/java/awt/SequencedEvent.java index 410437a5c9e..a671814a57b 100644 --- a/src/java.desktop/share/classes/java/awt/SequencedEvent.java +++ b/src/java.desktop/share/classes/java/awt/SequencedEvent.java @@ -26,8 +26,6 @@ package java.awt; import java.io.Serial; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.LinkedList; import sun.awt.AWTAccessor; @@ -80,13 +78,7 @@ class SequencedEvent extends AWTEvent implements ActiveEvent { return new SequencedEvent(event); } }); - AccessController.doPrivileged(new PrivilegedAction() { - public Object run() { - fxAppThreadIsDispatchThread = - "true".equals(System.getProperty("javafx.embed.singleThread")); - return null; - } - }); + fxAppThreadIsDispatchThread = "true".equals(System.getProperty("javafx.embed.singleThread")); } private static final class SequencedEventsFilter implements EventFilter { diff --git a/src/java.desktop/share/classes/java/awt/SplashScreen.java b/src/java.desktop/share/classes/java/awt/SplashScreen.java index 78ec4ed7aa0..7f522026b8a 100644 --- a/src/java.desktop/share/classes/java/awt/SplashScreen.java +++ b/src/java.desktop/share/classes/java/awt/SplashScreen.java @@ -129,13 +129,7 @@ public final class SplashScreen { } // SplashScreen class is now a singleton if (!wasClosed && theInstance == null) { - java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction() { - public Void run() { - System.loadLibrary("splashscreen"); - return null; - } - }); + System.loadLibrary("splashscreen"); long ptr = _getInstance(); if (ptr != 0 && _isVisible(ptr)) { theInstance = new SplashScreen(ptr); diff --git a/src/java.desktop/share/classes/java/awt/Toolkit.java b/src/java.desktop/share/classes/java/awt/Toolkit.java index 19b0ab9019a..d181fb6f536 100644 --- a/src/java.desktop/share/classes/java/awt/Toolkit.java +++ b/src/java.desktop/share/classes/java/awt/Toolkit.java @@ -56,8 +56,6 @@ import java.beans.PropertyChangeSupport; import java.io.File; import java.io.FileInputStream; import java.net.URL; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.EventListener; @@ -396,18 +394,12 @@ public abstract class Toolkit { * properties are set up properly before any classes dependent upon them * are initialized. */ - @SuppressWarnings("removal") private static void initAssistiveTechnologies() { // Get accessibility properties final String sep = File.separator; final Properties properties = new Properties(); - - atNames = java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction() { - public String run() { - // Try loading the per-user accessibility properties file. try { File propsFile = new File( @@ -459,9 +451,7 @@ public abstract class Toolkit { System.setProperty("javax.accessibility.assistive_technologies", classNames); } } - return classNames; - } - }); + atNames = classNames; } /** @@ -512,7 +502,6 @@ public abstract class Toolkit { * {@code null} it is ignored. All other errors are handled via an AWTError * exception. */ - @SuppressWarnings("removal") private static void loadAssistiveTechnologies() { // Load any assistive technologies if (atNames != null && !atNames.isBlank()) { @@ -521,20 +510,17 @@ public abstract class Toolkit { .map(String::trim) .collect(Collectors.toSet()); final Map providers = new HashMap<>(); - AccessController.doPrivileged((PrivilegedAction) () -> { - try { - for (AccessibilityProvider p : ServiceLoader.load(AccessibilityProvider.class, cl)) { - String name = p.getName(); - if (names.contains(name) && !providers.containsKey(name)) { - p.activate(); - providers.put(name, p); - } + try { + for (AccessibilityProvider p : ServiceLoader.load(AccessibilityProvider.class, cl)) { + String name = p.getName(); + if (names.contains(name) && !providers.containsKey(name)) { + p.activate(); + providers.put(name, p); } - } catch (java.util.ServiceConfigurationError | Exception e) { - newAWTError(e, "Could not load or activate service provider"); } - return null; - }); + } catch (java.util.ServiceConfigurationError | Exception e) { + newAWTError(e, "Could not load or activate service provider"); + } names.stream() .filter(n -> !providers.containsKey(n)) .forEach(Toolkit::fallbackToLoadClassForAT); @@ -1302,16 +1288,10 @@ public abstract class Toolkit { * directly. -hung */ private static boolean loaded = false; - @SuppressWarnings({"removal", "restricted"}) + @SuppressWarnings("restricted") static void loadLibraries() { if (!loaded) { - java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction() { - public Void run() { - System.loadLibrary("awt"); - return null; - } - }); + System.loadLibrary("awt"); loaded = true; } } @@ -1320,7 +1300,6 @@ public abstract class Toolkit { initStatic(); } - @SuppressWarnings("removal") private static void initStatic() { AWTAccessor.setToolkitAccessor( new AWTAccessor.ToolkitAccessor() { @@ -1330,17 +1309,11 @@ public abstract class Toolkit { } }); - java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction() { - public Void run() { - try { - resources = ResourceBundle.getBundle("sun.awt.resources.awt"); - } catch (MissingResourceException e) { - // No resource file; defaults will be used. - } - return null; - } - }); + try { + resources = ResourceBundle.getBundle("sun.awt.resources.awt"); + } catch (MissingResourceException e) { + // No resource file; defaults will be used. + } // ensure that the proper libraries are loaded loadLibraries(); diff --git a/src/java.desktop/share/classes/java/awt/WaitDispatchSupport.java b/src/java.desktop/share/classes/java/awt/WaitDispatchSupport.java index d239a600697..2968c9ad3ef 100644 --- a/src/java.desktop/share/classes/java/awt/WaitDispatchSupport.java +++ b/src/java.desktop/share/classes/java/awt/WaitDispatchSupport.java @@ -29,9 +29,6 @@ import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicBoolean; -import java.security.PrivilegedAction; -import java.security.AccessController; - import sun.awt.PeerEvent; import sun.util.logging.PlatformLogger; @@ -230,13 +227,7 @@ class WaitDispatchSupport implements SecondaryLoop { // The event will be handled after the new event pump // starts. Thus, the enter() method will not hang. // - // Event pump should be privileged. See 6300270. - AccessController.doPrivileged(new PrivilegedAction() { - public Void run() { - run.run(); - return null; - } - }); + run.run(); } else { if (log.isLoggable(PlatformLogger.Level.FINEST)) { log.finest("On non-dispatch thread: " + currentThread); diff --git a/src/java.desktop/share/classes/java/awt/Window.java b/src/java.desktop/share/classes/java/awt/Window.java index a1e9d48d9d4..1a5395431ab 100644 --- a/src/java.desktop/share/classes/java/awt/Window.java +++ b/src/java.desktop/share/classes/java/awt/Window.java @@ -48,7 +48,6 @@ import java.io.Serial; import java.io.Serializable; import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; -import java.security.AccessController; import java.util.ArrayList; import java.util.Arrays; import java.util.EventListener; @@ -414,13 +413,9 @@ public class Window extends Container implements Accessible { initIDs(); } - @SuppressWarnings("removal") - String s = java.security.AccessController.doPrivileged( - new GetPropertyAction("java.awt.syncLWRequests")); + String s = System.getProperty("java.awt.syncLWRequests"); systemSyncLWRequests = "true".equals(s); - @SuppressWarnings("removal") - String s2 = java.security.AccessController.doPrivileged( - new GetPropertyAction("java.awt.Window.locationByPlatform")); + String s2 = System.getProperty("java.awt.Window.locationByPlatform"); locationByPlatformProp = "true".equals(s2); } @@ -505,7 +500,6 @@ public class Window extends Container implements Accessible { weakThis = new WeakReference(this); addToWindowList(); - setWarningString(); this.cursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR); this.visible = false; @@ -1376,24 +1370,6 @@ public class Window extends Container implements Accessible { return warningString; } - @SuppressWarnings("removal") - private void setWarningString() { - warningString = null; - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - try { - sm.checkPermission(AWTPermissions.TOPLEVEL_WINDOW_PERMISSION); - } catch (SecurityException se) { - // make sure the privileged action is only - // for getting the property! We don't want the - // above checkPermission call to always succeed! - warningString = AccessController.doPrivileged( - new GetPropertyAction("awt.appletWarning", - "Java Applet Window")); - } - } - } - /** * Gets the {@code Locale} object that is associated * with this window, if the locale has been set. @@ -2978,7 +2954,6 @@ public class Window extends Container implements Accessible { // user's code. // private void initDeserializedWindow() { - setWarningString(); inputContextLock = new Object(); // Deserialized Windows are not yet visible. diff --git a/src/java.desktop/share/classes/java/awt/color/ICC_Profile.java b/src/java.desktop/share/classes/java/awt/color/ICC_Profile.java index b4e71b3c839..729d8123a24 100644 --- a/src/java.desktop/share/classes/java/awt/color/ICC_Profile.java +++ b/src/java.desktop/share/classes/java/awt/color/ICC_Profile.java @@ -48,8 +48,6 @@ import java.io.ObjectStreamException; import java.io.OutputStream; import java.io.Serial; import java.io.Serializable; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.Objects; import java.util.StringTokenizer; @@ -1341,13 +1339,8 @@ public sealed class ICC_Profile implements Serializable * fileName. If there is no built-in profile with such name, then the method * returns {@code null}. */ - @SuppressWarnings("removal") private static InputStream getStandardProfileInputStream(String fileName) { - return AccessController.doPrivileged( - (PrivilegedAction) () -> { - return PCMM.class.getResourceAsStream("profiles/" + fileName); - }, null, new FilePermission("<>", "read"), - new RuntimePermission("accessSystemModules")); + return PCMM.class.getResourceAsStream("profiles/" + fileName); } /** diff --git a/src/java.desktop/share/classes/java/awt/dnd/DragSource.java b/src/java.desktop/share/classes/java/awt/dnd/DragSource.java index 01945662e46..8477bc6376d 100644 --- a/src/java.desktop/share/classes/java/awt/dnd/DragSource.java +++ b/src/java.desktop/share/classes/java/awt/dnd/DragSource.java @@ -40,13 +40,11 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serial; import java.io.Serializable; -import java.security.AccessController; import java.util.EventListener; import sun.awt.AWTAccessor; import sun.awt.AWTAccessor.DragSourceContextAccessor; import sun.awt.dnd.SunDragSourceContextPeer; -import sun.security.action.GetIntegerAction; /** * The {@code DragSource} is the entity responsible @@ -908,8 +906,7 @@ public class DragSource implements Serializable { */ public static int getDragThreshold() { @SuppressWarnings("removal") - int ts = AccessController.doPrivileged( - new GetIntegerAction("awt.dnd.drag.threshold", 0)).intValue(); + int ts = Integer.getInteger("awt.dnd.drag.threshold", 0); if (ts > 0) { return ts; } else { diff --git a/src/java.desktop/share/classes/java/awt/event/NativeLibLoader.java b/src/java.desktop/share/classes/java/awt/event/NativeLibLoader.java index 27a2c439747..8b0d46d05e0 100644 --- a/src/java.desktop/share/classes/java/awt/event/NativeLibLoader.java +++ b/src/java.desktop/share/classes/java/awt/event/NativeLibLoader.java @@ -52,14 +52,8 @@ class NativeLibLoader { * For now, we know it's done by the implementation, and we assume * that the name of the library is "awt". -br. */ - @SuppressWarnings({"removal", "restricted"}) + @SuppressWarnings("restricted") static void loadLibraries() { - java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction() { - public Void run() { - System.loadLibrary("awt"); - return null; - } - }); + System.loadLibrary("awt"); } } diff --git a/src/java.desktop/share/classes/java/awt/image/BufferedImage.java b/src/java.desktop/share/classes/java/awt/image/BufferedImage.java index cfbebd68710..09c96a6560f 100644 --- a/src/java.desktop/share/classes/java/awt/image/BufferedImage.java +++ b/src/java.desktop/share/classes/java/awt/image/BufferedImage.java @@ -31,8 +31,6 @@ import java.awt.Point; import java.awt.Rectangle; import java.awt.Transparency; import java.awt.color.ColorSpace; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.Hashtable; import java.util.Set; import java.util.Vector; @@ -800,26 +798,16 @@ public class BufferedImage extends java.awt.Image } // else if ((raster instanceof ByteComponentRaster) && } - @SuppressWarnings("removal") private static boolean isStandard(ColorModel cm, WritableRaster wr) { final Class cmClass = cm.getClass(); final Class wrClass = wr.getClass(); final Class smClass = wr.getSampleModel().getClass(); - final PrivilegedAction checkClassLoadersAction = - new PrivilegedAction() - { + final ClassLoader std = System.class.getClassLoader(); - @Override - public Boolean run() { - final ClassLoader std = System.class.getClassLoader(); - - return (cmClass.getClassLoader() == std) && - (smClass.getClassLoader() == std) && - (wrClass.getClassLoader() == std); - } - }; - return AccessController.doPrivileged(checkClassLoadersAction); + return (cmClass.getClassLoader() == std) && + (smClass.getClassLoader() == std) && + (wrClass.getClassLoader() == std); } /** diff --git a/src/java.desktop/share/classes/java/awt/image/ColorModel.java b/src/java.desktop/share/classes/java/awt/image/ColorModel.java index a0aa8fddc10..b2d568cb09f 100644 --- a/src/java.desktop/share/classes/java/awt/image/ColorModel.java +++ b/src/java.desktop/share/classes/java/awt/image/ColorModel.java @@ -202,16 +202,10 @@ public abstract class ColorModel implements Transparency{ * that the name of the library is "awt". -br. */ private static boolean loaded = false; - @SuppressWarnings({"removal", "restricted"}) + @SuppressWarnings("restricted") static void loadLibraries() { if (!loaded) { - java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction() { - public Void run() { - System.loadLibrary("awt"); - return null; - } - }); + System.loadLibrary("awt"); loaded = true; } } diff --git a/src/java.desktop/share/classes/java/beans/EventHandler.java b/src/java.desktop/share/classes/java/beans/EventHandler.java index b3c630ae5c9..eb892f34985 100644 --- a/src/java.desktop/share/classes/java/beans/EventHandler.java +++ b/src/java.desktop/share/classes/java/beans/EventHandler.java @@ -28,9 +28,6 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Proxy; import java.lang.reflect.Method; -import java.security.AccessControlContext; -import java.security.AccessController; -import java.security.PrivilegedAction; import sun.reflect.misc.MethodUtil; import sun.reflect.misc.ReflectUtil; @@ -281,8 +278,6 @@ public class EventHandler implements InvocationHandler { private String action; private final String eventPropertyName; private final String listenerMethodName; - @SuppressWarnings("removal") - private final AccessControlContext acc = AccessController.getContext(); /** * Creates a new {@code EventHandler} object; @@ -421,20 +416,7 @@ public class EventHandler implements InvocationHandler { * * @see EventHandler */ - @SuppressWarnings("removal") public Object invoke(final Object proxy, final Method method, final Object[] arguments) { - AccessControlContext acc = this.acc; - if ((acc == null) && (System.getSecurityManager() != null)) { - throw new SecurityException("AccessControlContext is not set"); - } - return AccessController.doPrivileged(new PrivilegedAction() { - public Object run() { - return invokeInternal(proxy, method, arguments); - } - }, acc); - } - - private Object invokeInternal(Object proxy, Method method, Object[] arguments) { String methodName = method.getName(); if (method.getDeclaringClass() == Object.class) { // Handle the Object public methods. @@ -689,7 +671,7 @@ public class EventHandler implements InvocationHandler { * @see EventHandler * @see Proxy#newProxyInstance */ - @SuppressWarnings("removal") + @SuppressWarnings("unchecked") public static T create(Class listenerInterface, Object target, String action, String eventPropertyName, @@ -705,12 +687,7 @@ public class EventHandler implements InvocationHandler { } final ClassLoader loader = getClassLoader(listenerInterface); final Class[] interfaces = {listenerInterface}; - return AccessController.doPrivileged(new PrivilegedAction() { - @SuppressWarnings("unchecked") - public T run() { - return (T) Proxy.newProxyInstance(loader, interfaces, handler); - } - }); + return (T) Proxy.newProxyInstance(loader, interfaces, handler); } private static ClassLoader getClassLoader(Class type) { diff --git a/src/java.desktop/share/classes/java/beans/MetaData.java b/src/java.desktop/share/classes/java/beans/MetaData.java index e3cf55a85c5..0a12e7dcee8 100644 --- a/src/java.desktop/share/classes/java/beans/MetaData.java +++ b/src/java.desktop/share/classes/java/beans/MetaData.java @@ -41,9 +41,6 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.InvocationTargetException; -import java.security.AccessController; -import java.security.PrivilegedAction; - import java.util.*; import javax.swing.Box; @@ -1319,28 +1316,22 @@ static final class sun_swing_PrintColorUIResource_PersistenceDelegate extends Pe } } - @SuppressWarnings("removal") static Object getPrivateFieldValue(Object instance, String name) { Field field = fields.get(name); if (field == null) { int index = name.lastIndexOf('.'); final String className = name.substring(0, index); final String fieldName = name.substring(1 + index); - field = AccessController.doPrivileged(new PrivilegedAction() { - public Field run() { - try { - Field field = Class.forName(className).getDeclaredField(fieldName); - field.setAccessible(true); - return field; - } - catch (ClassNotFoundException exception) { - throw new IllegalStateException("Could not find class", exception); - } - catch (NoSuchFieldException exception) { - throw new IllegalStateException("Could not find field", exception); - } - } - }); + try { + field = Class.forName(className).getDeclaredField(fieldName); + field.setAccessible(true); + } + catch (ClassNotFoundException exception) { + throw new IllegalStateException("Could not find class", exception); + } + catch (NoSuchFieldException exception) { + throw new IllegalStateException("Could not find field", exception); + } fields.put(name, field); } try { diff --git a/src/java.desktop/share/classes/java/beans/SimpleBeanInfo.java b/src/java.desktop/share/classes/java/beans/SimpleBeanInfo.java index 34ca9d20403..7b6fd131c24 100644 --- a/src/java.desktop/share/classes/java/beans/SimpleBeanInfo.java +++ b/src/java.desktop/share/classes/java/beans/SimpleBeanInfo.java @@ -29,8 +29,6 @@ import java.awt.Image; import java.awt.Toolkit; import java.awt.image.ImageProducer; import java.net.URL; -import java.security.AccessController; -import java.security.PrivilegedAction; /** * This is a support class to make it easier for people to provide @@ -154,10 +152,8 @@ public class SimpleBeanInfo implements BeanInfo { * @return an image object. May be null if the load failed. * @see java.beans.SimpleBeanInfo#loadImage(String) */ - @SuppressWarnings("removal") private Image loadStandardImage(final String resourceName) { - return AccessController.doPrivileged( - (PrivilegedAction) () -> loadImage(resourceName)); + return loadImage(resourceName); } /** diff --git a/src/java.desktop/share/classes/java/beans/Statement.java b/src/java.desktop/share/classes/java/beans/Statement.java index d079a0b24a9..bf1c62be78b 100644 --- a/src/java.desktop/share/classes/java/beans/Statement.java +++ b/src/java.desktop/share/classes/java/beans/Statement.java @@ -29,10 +29,6 @@ import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.security.AccessControlContext; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; import com.sun.beans.finder.ClassFinder; import com.sun.beans.finder.ConstructorFinder; @@ -69,8 +65,6 @@ public class Statement { } }; - @SuppressWarnings("removal") - private final AccessControlContext acc = AccessController.getContext(); private final Object target; private final String methodName; private final Object[] arguments; @@ -174,28 +168,7 @@ public class Statement { invoke(); } - @SuppressWarnings("removal") Object invoke() throws Exception { - AccessControlContext acc = this.acc; - if ((acc == null) && (System.getSecurityManager() != null)) { - throw new SecurityException("AccessControlContext is not set"); - } - try { - return AccessController.doPrivileged( - new PrivilegedExceptionAction() { - public Object run() throws Exception { - return invokeInternal(); - } - }, - acc - ); - } - catch (PrivilegedActionException exception) { - throw exception.getException(); - } - } - - private Object invokeInternal() throws Exception { Object target = getTarget(); String methodName = getMethodName(); diff --git a/src/java.desktop/share/classes/java/beans/XMLDecoder.java b/src/java.desktop/share/classes/java/beans/XMLDecoder.java index 8a7e6b9984a..febba84d2a1 100644 --- a/src/java.desktop/share/classes/java/beans/XMLDecoder.java +++ b/src/java.desktop/share/classes/java/beans/XMLDecoder.java @@ -29,9 +29,6 @@ import com.sun.beans.decoder.DocumentHandler; import java.io.Closeable; import java.io.InputStream; import java.io.IOException; -import java.security.AccessControlContext; -import java.security.AccessController; -import java.security.PrivilegedAction; import org.xml.sax.InputSource; import org.xml.sax.helpers.DefaultHandler; @@ -64,8 +61,6 @@ import org.xml.sax.helpers.DefaultHandler; * @author Philip Milne */ public class XMLDecoder implements AutoCloseable { - @SuppressWarnings("removal") - private final AccessControlContext acc = AccessController.getContext(); private final DocumentHandler handler = new DocumentHandler(); private final InputSource input; private Object owner; @@ -189,21 +184,12 @@ public class XMLDecoder implements AutoCloseable { } } - @SuppressWarnings("removal") private boolean parsingComplete() { if (this.input == null) { return false; } if (this.array == null) { - if ((this.acc == null) && (null != System.getSecurityManager())) { - throw new SecurityException("AccessControlContext is not set"); - } - AccessController.doPrivileged(new PrivilegedAction() { - public Void run() { - XMLDecoder.this.handler.parse(XMLDecoder.this.input); - return null; - } - }, this.acc); + XMLDecoder.this.handler.parse(XMLDecoder.this.input); this.array = this.handler.getObjects(); } return true; diff --git a/src/java.desktop/share/classes/javax/swing/plaf/nimbus/SynthPainterImpl.java b/src/java.desktop/share/classes/javax/swing/plaf/nimbus/SynthPainterImpl.java index 9dfd422f6dd..96ef2541c7b 100644 --- a/src/java.desktop/share/classes/javax/swing/plaf/nimbus/SynthPainterImpl.java +++ b/src/java.desktop/share/classes/javax/swing/plaf/nimbus/SynthPainterImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -24,12 +24,21 @@ */ package javax.swing.plaf.nimbus; -import java.awt.*; +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Graphics2D; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.image.BufferedImage; -import java.util.*; -import javax.swing.*; +import javax.swing.JDesktopPane; +import javax.swing.JSlider; +import javax.swing.JSplitPane; +import javax.swing.JTabbedPane; +import javax.swing.Painter; +import javax.swing.SwingConstants; +import javax.swing.UIManager; +import javax.swing.plaf.UIResource; import javax.swing.plaf.synth.SynthContext; import javax.swing.plaf.synth.SynthPainter; import javax.swing.plaf.synth.SynthConstants; @@ -531,7 +540,11 @@ class SynthPainterImpl extends SynthPainter { public void paintDesktopPaneBackground(SynthContext context, Graphics g, int x, int y, int w, int h) { - paintBackground(context, g, x, y, w, h, null); + if (context.getComponent() instanceof JDesktopPane pane) { + if (pane.getBackground() instanceof UIResource) { + paintBackground(context, g, x, y, w, h, null); + } + } } /** diff --git a/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/CKeyStore.java b/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/CKeyStore.java index 4e352bf4950..41580151f2b 100644 --- a/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/CKeyStore.java +++ b/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/CKeyStore.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -29,15 +29,12 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.security.AccessController; import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyStoreSpi; import java.security.KeyStoreException; -import java.security.PrivilegedAction; import java.security.UnrecoverableKeyException; import java.security.NoSuchAlgorithmException; -import java.security.SecurityPermission; import java.security.cert.X509Certificate; import java.security.cert.Certificate; import java.security.cert.CertificateException; @@ -242,9 +239,7 @@ abstract class CKeyStore extends KeyStoreSpi { CKeyStore(String storeName, int storeLocation) { // Get the compatibility mode - @SuppressWarnings("removal") - String prop = AccessController.doPrivileged( - (PrivilegedAction) () -> System.getProperty(KEYSTORE_COMPATIBILITY_MODE_PROP)); + String prop = System.getProperty(KEYSTORE_COMPATIBILITY_MODE_PROP); if ("false".equalsIgnoreCase(prop)) { keyStoreCompatibilityMode = false; @@ -695,10 +690,6 @@ abstract class CKeyStore extends KeyStoreSpi { * the integrity of the keystore cannot be found * @exception CertificateException if any of the certificates in the * keystore could not be loaded - * @exception SecurityException if the security check for - * SecurityPermission("authProvider.name") does not - * pass, where name is the value returned by - * this provider's getName method. */ public void engineLoad(InputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException { @@ -710,16 +701,6 @@ abstract class CKeyStore extends KeyStoreSpi { throw new IOException("Keystore password must be null"); } - /* - * Use the same security check as AuthProvider.login - */ - @SuppressWarnings("removal") - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - sm.checkPermission(new SecurityPermission( - "authProvider.SunMSCAPI")); - } - // Clear all key entries entries.clear(); diff --git a/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/SunMSCAPI.java b/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/SunMSCAPI.java index e57fe331f28..8ee16cf6bb8 100644 --- a/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/SunMSCAPI.java +++ b/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/SunMSCAPI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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,8 +25,6 @@ package sun.security.mscapi; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.security.Provider; import java.security.NoSuchAlgorithmException; import java.security.InvalidParameterException; @@ -50,14 +48,14 @@ public final class SunMSCAPI extends Provider { private static final String INFO = "Sun's Microsoft Crypto API provider"; static { - @SuppressWarnings({"removal", "restricted"}) - var dummy = AccessController.doPrivileged(new PrivilegedAction() { - public Void run() { - System.loadLibrary("sunmscapi"); - return null; - } - }); + loadLibrary(); } + + @SuppressWarnings("restricted") + private static void loadLibrary() { + System.loadLibrary("sunmscapi"); + } + private static class ProviderServiceA extends ProviderService { ProviderServiceA(Provider p, String type, String algo, String cn, HashMap attrs) { @@ -148,119 +146,113 @@ public final class SunMSCAPI extends Provider { } } - @SuppressWarnings("removal") public SunMSCAPI() { super("SunMSCAPI", PROVIDER_VER, INFO); final Provider p = this; - AccessController.doPrivileged(new PrivilegedAction() { - public Void run() { - /* - * Secure random - */ - HashMap srattrs = new HashMap<>(1); - srattrs.put("ThreadSafe", "true"); - putService(new ProviderService(p, "SecureRandom", - "Windows-PRNG", "sun.security.mscapi.PRNG", - null, srattrs)); + /* + * Secure random + */ + HashMap srattrs = new HashMap<>(1); + srattrs.put("ThreadSafe", "true"); + putService(new ProviderService(p, "SecureRandom", + "Windows-PRNG", "sun.security.mscapi.PRNG", + null, srattrs)); - /* - * Key store - */ - putService(new ProviderService(p, "KeyStore", - "Windows-MY", "sun.security.mscapi.CKeyStore$MY")); - putService(new ProviderService(p, "KeyStore", - "Windows-MY-CURRENTUSER", "sun.security.mscapi.CKeyStore$MY")); - putService(new ProviderService(p, "KeyStore", - "Windows-ROOT", "sun.security.mscapi.CKeyStore$ROOT")); - putService(new ProviderService(p, "KeyStore", - "Windows-ROOT-CURRENTUSER", "sun.security.mscapi.CKeyStore$ROOT")); - putService(new ProviderService(p, "KeyStore", - "Windows-MY-LOCALMACHINE", "sun.security.mscapi.CKeyStore$MYLocalMachine")); - putService(new ProviderService(p, "KeyStore", - "Windows-ROOT-LOCALMACHINE", "sun.security.mscapi.CKeyStore$ROOTLocalMachine")); + /* + * Key store + */ + putService(new ProviderService(p, "KeyStore", + "Windows-MY", "sun.security.mscapi.CKeyStore$MY")); + putService(new ProviderService(p, "KeyStore", + "Windows-MY-CURRENTUSER", "sun.security.mscapi.CKeyStore$MY")); + putService(new ProviderService(p, "KeyStore", + "Windows-ROOT", "sun.security.mscapi.CKeyStore$ROOT")); + putService(new ProviderService(p, "KeyStore", + "Windows-ROOT-CURRENTUSER", "sun.security.mscapi.CKeyStore$ROOT")); + putService(new ProviderService(p, "KeyStore", + "Windows-MY-LOCALMACHINE", "sun.security.mscapi.CKeyStore$MYLocalMachine")); + putService(new ProviderService(p, "KeyStore", + "Windows-ROOT-LOCALMACHINE", "sun.security.mscapi.CKeyStore$ROOTLocalMachine")); - /* - * Signature engines - */ - HashMap attrs = new HashMap<>(1); - attrs.put("SupportedKeyClasses", "sun.security.mscapi.CKey"); + /* + * Signature engines + */ + HashMap attrs = new HashMap<>(1); + attrs.put("SupportedKeyClasses", "sun.security.mscapi.CKey"); - // NONEwithRSA must be supplied with a pre-computed message digest. - // Only the following digest algorithms are supported: MD5, SHA-1, - // SHA-256, SHA-384, SHA-512 and a special-purpose digest - // algorithm which is a concatenation of SHA-1 and MD5 digests. - putService(new ProviderService(p, "Signature", - "NONEwithRSA", "sun.security.mscapi.CSignature$NONEwithRSA", - null, attrs)); - putService(new ProviderService(p, "Signature", - "SHA1withRSA", "sun.security.mscapi.CSignature$SHA1withRSA", - null, attrs)); - putService(new ProviderServiceA(p, "Signature", - "SHA256withRSA", - "sun.security.mscapi.CSignature$SHA256withRSA", - attrs)); - putService(new ProviderServiceA(p, "Signature", - "SHA384withRSA", - "sun.security.mscapi.CSignature$SHA384withRSA", - attrs)); - putService(new ProviderServiceA(p, "Signature", - "SHA512withRSA", - "sun.security.mscapi.CSignature$SHA512withRSA", - attrs)); - putService(new ProviderServiceA(p, "Signature", - "RSASSA-PSS", "sun.security.mscapi.CSignature$PSS", - attrs)); - putService(new ProviderService(p, "Signature", - "MD5withRSA", "sun.security.mscapi.CSignature$MD5withRSA", - null, attrs)); - putService(new ProviderService(p, "Signature", - "MD2withRSA", "sun.security.mscapi.CSignature$MD2withRSA", - null, attrs)); - putService(new ProviderServiceA(p, "Signature", - "SHA1withECDSA", - "sun.security.mscapi.CSignature$SHA1withECDSA", - attrs)); - putService(new ProviderServiceA(p, "Signature", - "SHA224withECDSA", - "sun.security.mscapi.CSignature$SHA224withECDSA", - attrs)); - putService(new ProviderServiceA(p, "Signature", - "SHA256withECDSA", - "sun.security.mscapi.CSignature$SHA256withECDSA", - attrs)); - putService(new ProviderServiceA(p, "Signature", - "SHA384withECDSA", - "sun.security.mscapi.CSignature$SHA384withECDSA", - attrs)); - putService(new ProviderServiceA(p, "Signature", - "SHA512withECDSA", - "sun.security.mscapi.CSignature$SHA512withECDSA", - attrs)); - /* - * Key Pair Generator engines - */ - attrs.clear(); - attrs.put("KeySize", "16384"); - putService(new ProviderService(p, "KeyPairGenerator", - "RSA", "sun.security.mscapi.CKeyPairGenerator$RSA", - null, attrs)); + // NONEwithRSA must be supplied with a pre-computed message digest. + // Only the following digest algorithms are supported: MD5, SHA-1, + // SHA-256, SHA-384, SHA-512 and a special-purpose digest + // algorithm which is a concatenation of SHA-1 and MD5 digests. + putService(new ProviderService(p, "Signature", + "NONEwithRSA", "sun.security.mscapi.CSignature$NONEwithRSA", + null, attrs)); + putService(new ProviderService(p, "Signature", + "SHA1withRSA", "sun.security.mscapi.CSignature$SHA1withRSA", + null, attrs)); + putService(new ProviderServiceA(p, "Signature", + "SHA256withRSA", + "sun.security.mscapi.CSignature$SHA256withRSA", + attrs)); + putService(new ProviderServiceA(p, "Signature", + "SHA384withRSA", + "sun.security.mscapi.CSignature$SHA384withRSA", + attrs)); + putService(new ProviderServiceA(p, "Signature", + "SHA512withRSA", + "sun.security.mscapi.CSignature$SHA512withRSA", + attrs)); + putService(new ProviderServiceA(p, "Signature", + "RSASSA-PSS", "sun.security.mscapi.CSignature$PSS", + attrs)); + putService(new ProviderService(p, "Signature", + "MD5withRSA", "sun.security.mscapi.CSignature$MD5withRSA", + null, attrs)); + putService(new ProviderService(p, "Signature", + "MD2withRSA", "sun.security.mscapi.CSignature$MD2withRSA", + null, attrs)); + putService(new ProviderServiceA(p, "Signature", + "SHA1withECDSA", + "sun.security.mscapi.CSignature$SHA1withECDSA", + attrs)); + putService(new ProviderServiceA(p, "Signature", + "SHA224withECDSA", + "sun.security.mscapi.CSignature$SHA224withECDSA", + attrs)); + putService(new ProviderServiceA(p, "Signature", + "SHA256withECDSA", + "sun.security.mscapi.CSignature$SHA256withECDSA", + attrs)); + putService(new ProviderServiceA(p, "Signature", + "SHA384withECDSA", + "sun.security.mscapi.CSignature$SHA384withECDSA", + attrs)); + putService(new ProviderServiceA(p, "Signature", + "SHA512withECDSA", + "sun.security.mscapi.CSignature$SHA512withECDSA", + attrs)); + /* + * Key Pair Generator engines + */ + attrs.clear(); + attrs.put("KeySize", "16384"); + putService(new ProviderService(p, "KeyPairGenerator", + "RSA", "sun.security.mscapi.CKeyPairGenerator$RSA", + null, attrs)); - /* - * Cipher engines - */ - attrs.clear(); - attrs.put("SupportedModes", "ECB"); - attrs.put("SupportedPaddings", "PKCS1PADDING"); - attrs.put("SupportedKeyClasses", "sun.security.mscapi.CKey"); - putService(new ProviderService(p, "Cipher", - "RSA", "sun.security.mscapi.CRSACipher", - null, attrs)); - putService(new ProviderService(p, "Cipher", - "RSA/ECB/PKCS1Padding", "sun.security.mscapi.CRSACipher", - null, attrs)); - return null; - } - }); + /* + * Cipher engines + */ + attrs.clear(); + attrs.put("SupportedModes", "ECB"); + attrs.put("SupportedPaddings", "PKCS1PADDING"); + attrs.put("SupportedKeyClasses", "sun.security.mscapi.CKey"); + putService(new ProviderService(p, "Cipher", + "RSA", "sun.security.mscapi.CRSACipher", + null, attrs)); + putService(new ProviderService(p, "Cipher", + "RSA/ECB/PKCS1Padding", "sun.security.mscapi.CRSACipher", + null, attrs)); } } diff --git a/test/hotspot/jtreg/ProblemList-AotJdk.txt b/test/hotspot/jtreg/ProblemList-AotJdk.txt new file mode 100644 index 00000000000..2528f8d377e --- /dev/null +++ b/test/hotspot/jtreg/ProblemList-AotJdk.txt @@ -0,0 +1,18 @@ +runtime/modules/PatchModule/PatchModuleClassList.java 0000000 generic-all +runtime/NMT/NMTWithCDS.java 0000000 generic-all +runtime/symbols/TestSharedArchiveConfigFile.java 0000000 generic-all + +gc/arguments/TestSerialHeapSizeFlags.java 0000000 generic-all +gc/TestAllocateHeapAtMultiple.java 0000000 generic-all +gc/TestAllocateHeapAt.java 0000000 generic-all + +# use -Xshare +serviceability/sa/ClhsdbCDSJstackPrintAll.java 0000000 generic-all +serviceability/sa/ClhsdbCDSCore.java 0000000 generic-all +serviceability/sa/CDSJMapClstats.java 0000000 generic-all +compiler/intrinsics/klass/TestIsPrimitive.java 0000000 generic-all + +# This test is incompatible with AOTClassLinking. +# It has the assumption about unresolved Integer. +# However when AOTClassLinking is enabled, Integer is always resolved at JVM start-up. +compiler/ciReplay/TestInliningProtectionDomain.java 0000000 generic-all diff --git a/test/hotspot/jtreg/TEST.ROOT b/test/hotspot/jtreg/TEST.ROOT index af97ff465de..1e5094bd116 100644 --- a/test/hotspot/jtreg/TEST.ROOT +++ b/test/hotspot/jtreg/TEST.ROOT @@ -74,6 +74,7 @@ requires.properties= \ vm.rtm.compiler \ vm.cds \ vm.cds.custom.loaders \ + vm.cds.supports.aot.class.linking \ vm.cds.write.archived.java.heap \ vm.continuations \ vm.jvmti \ diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index 575c0caa674..718ecf610f1 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -56,6 +56,7 @@ hotspot_runtime_no_cds = \ hotspot_runtime_non_cds_mode = \ runtime \ -runtime/cds/CheckSharingWithDefaultArchive.java \ + -runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java \ -runtime/cds/appcds/dynamicArchive/DynamicSharedSymbols.java \ -runtime/cds/appcds/dynamicArchive/TestAutoCreateSharedArchive.java \ -runtime/cds/appcds/jcmd @@ -430,6 +431,7 @@ hotspot_cds_only = \ hotspot_appcds_dynamic = \ runtime/cds/appcds/ \ + -runtime/cds/appcds/applications \ -runtime/cds/appcds/cacheObject \ -runtime/cds/appcds/complexURI \ -runtime/cds/appcds/customLoader \ @@ -447,10 +449,12 @@ hotspot_appcds_dynamic = \ -runtime/cds/appcds/jvmti/redefineClasses/OldClassAndRedefineClass.java \ -runtime/cds/appcds/lambdaForm/DefaultClassListLFInvokers.java \ -runtime/cds/appcds/methodHandles \ + -runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java \ -runtime/cds/appcds/sharedStrings \ -runtime/cds/appcds/resolvedConstants \ -runtime/cds/appcds/ArchiveRelocationTest.java \ -runtime/cds/appcds/BadBSM.java \ + -runtime/cds/appcds/CommandLineFlagCombo.java \ -runtime/cds/appcds/DumpClassList.java \ -runtime/cds/appcds/DumpClassListWithLF.java \ -runtime/cds/appcds/DumpRuntimeClassesTest.java \ @@ -518,6 +522,61 @@ hotspot_cds_epsilongc = \ runtime/cds/appcds/jigsaw \ runtime/cds/appcds/loaderConstraints +# Run CDS tests with -XX:+AOTClassLinking. This should include most CDS tests, except for +# those that rely on redefining classes that are already archived. +hotspot_aot_classlinking = \ + runtime/cds \ + -runtime/cds/appcds/aotClassLinking \ + -runtime/cds/appcds/BadBSM.java \ + -runtime/cds/appcds/cacheObject/ArchivedIntegerCacheTest.java \ + -runtime/cds/appcds/cacheObject/ArchivedModuleCompareTest.java \ + -runtime/cds/appcds/CDSandJFR.java \ + -runtime/cds/appcds/customLoader/HelloCustom_JFR.java \ + -runtime/cds/appcds/customLoader/ParallelTestMultiFP.java \ + -runtime/cds/appcds/customLoader/ParallelTestSingleFP.java \ + -runtime/cds/appcds/customLoader/SameNameInTwoLoadersTest.java \ + -runtime/cds/appcds/DumpClassListWithLF.java \ + -runtime/cds/appcds/dynamicArchive/ModulePath.java \ + -runtime/cds/appcds/dynamicArchive/LambdaInBaseArchive.java \ + -runtime/cds/appcds/dynamicArchive/LambdasInTwoArchives.java \ + -runtime/cds/appcds/HelloExtTest.java \ + -runtime/cds/appcds/javaldr/AnonVmClassesDuringDump.java \ + -runtime/cds/appcds/javaldr/GCDuringDump.java \ + -runtime/cds/appcds/javaldr/LockDuringDump.java \ + -runtime/cds/appcds/jigsaw/classpathtests/EmptyClassInBootClassPath.java \ + -runtime/cds/appcds/jigsaw/JigsawOptionsCombo.java \ + -runtime/cds/appcds/jigsaw/modulepath/AddOpens.java \ + -runtime/cds/appcds/jigsaw/modulepath/AddModules.java \ + -runtime/cds/appcds/jigsaw/modulepath/JvmtiAddPath.java \ + -runtime/cds/appcds/jigsaw/modulepath/MainModuleOnly.java \ + -runtime/cds/appcds/jigsaw/modulepath/ModulePathAndCP.java \ + -runtime/cds/appcds/jigsaw/modulepath/ModulePathAndCP_JFR.java \ + -runtime/cds/appcds/jigsaw/modulepath/ModulePathAndFMG.java \ + -runtime/cds/appcds/jigsaw/modulepath/OptimizeModuleHandlingTest.java \ + -runtime/cds/appcds/jigsaw/overridetests/OverrideTests.java \ + -runtime/cds/appcds/jigsaw/RedefineClassesInModuleGraph.java \ + -runtime/cds/appcds/JvmtiAddPath.java \ + -runtime/cds/appcds/jvmti \ + -runtime/cds/appcds/LambdaProxyClasslist.java \ + -runtime/cds/appcds/loaderConstraints/LoaderConstraintsTest.java \ + -runtime/cds/appcds/redefineClass \ + -runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java \ + -runtime/cds/appcds/resolvedConstants/AOTLinkedVarHandles.java \ + -runtime/cds/appcds/resolvedConstants/ResolvedConstants.java \ + -runtime/cds/appcds/RewriteBytecodesTest.java \ + -runtime/cds/appcds/SpecifySysLoaderProp.java \ + -runtime/cds/appcds/StaticArchiveWithLambda.java \ + -runtime/cds/appcds/TestEpsilonGCWithCDS.java \ + -runtime/cds/appcds/TestParallelGCWithCDS.java \ + -runtime/cds/appcds/TestSerialGCWithCDS.java \ + -runtime/cds/appcds/TestZGCWithCDS.java \ + -runtime/cds/appcds/TestWithProfiler.java \ + -runtime/cds/serviceability/ReplaceCriticalClassesForSubgraphs.java \ + -runtime/cds/serviceability/ReplaceCriticalClasses.java \ + -runtime/cds/serviceability/transformRelatedClasses/TransformInterfaceAndImplementor.java \ + -runtime/cds/serviceability/transformRelatedClasses/TransformSuperAndSubClasses.java \ + -runtime/cds/serviceability/transformRelatedClasses/TransformSuperSubTwoPckgs.java + # needs -nativepath:/images/test/hotspot/jtreg/native/ hotspot_metaspace = \ gtest/MetaspaceGtests.java \ @@ -537,6 +596,10 @@ tier1_runtime_appcds_exclude = \ runtime/cds/appcds/ \ -:tier1_runtime_appcds +tier1_runtime_no_cds = \ + :tier1_runtime \ + -runtime/cds + # This group should be executed with "jtreg -Dtest.cds.run.with.jfr=true ..." # to test interaction between AppCDS and JFR. It also has the side effect of # testing JVMTI ClassFileLoadHook. diff --git a/test/hotspot/jtreg/runtime/cds/SharedBaseAddress.java b/test/hotspot/jtreg/runtime/cds/SharedBaseAddress.java index a7f31cf1a08..49265680ef2 100644 --- a/test/hotspot/jtreg/runtime/cds/SharedBaseAddress.java +++ b/test/hotspot/jtreg/runtime/cds/SharedBaseAddress.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -67,8 +67,23 @@ import jdk.test.lib.cds.CDSTestUtils; import jdk.test.lib.cds.CDSOptions; import jdk.test.lib.Platform; import jdk.test.lib.process.OutputAnalyzer; +import jtreg.SkippedException; public class SharedBaseAddress { + static final boolean skipUncompressedOopsTests; + static boolean checkSkipUncompressedOopsTests(String prop) { + String opts = System.getProperty(prop); + return opts.contains("+AOTClassLinking") && + opts.matches(".*[+]Use[A-Za-z]+GC.*") && !opts.contains("+UseG1GC"); + } + static { + // AOTClassLinking requires the ability to load archived heap objects. However, + // due to JDK-8341371, only G1GC supports loading archived heap objects + // with uncompressed oops. + skipUncompressedOopsTests = + checkSkipUncompressedOopsTests("test.vm.opts") || + checkSkipUncompressedOopsTests("test.java.opts"); + } // shared base address test table for {32, 64}bit VM private static final String[] testTableShared = { @@ -100,6 +115,10 @@ public class SharedBaseAddress { int end = args[0].equals("0") ? mid : testTable.length; boolean provoke = (args.length > 1 && args[1].equals("provoke")); + if (provoke && skipUncompressedOopsTests) { + throw new SkippedException("Test skipped due to JDK-8341371"); + } + // provoke == true: we want to increase the chance that mapping the generated archive at the designated base // succeeds, to test Klass pointer encoding at that weird location. We do this by sizing heap + class space // small, and by switching off compressed oops. diff --git a/test/hotspot/jtreg/runtime/cds/appcds/AOTFlags.java b/test/hotspot/jtreg/runtime/cds/appcds/AOTFlags.java new file mode 100644 index 00000000000..3a678eefc5b --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/AOTFlags.java @@ -0,0 +1,203 @@ +/* + * 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 "AOT" aliases for traditional CDS command-line options + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes + * @build Hello + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar hello.jar Hello + * @run driver AOTFlags + */ + +import jdk.test.lib.cds.CDSTestUtils; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class AOTFlags { + static String appJar = ClassFileInstaller.getJarPath("hello.jar"); + static String aotConfigFile = "hello.aotconfig"; + static String aotCacheFile = "hello.aot"; + static String helloClass = "Hello"; + + public static void main(String[] args) throws Exception { + positiveTests(); + negativeTests(); + } + + static void positiveTests() throws Exception { + // (1) Training Run + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTMode=record", + "-XX:AOTConfiguration=" + aotConfigFile, + "-cp", appJar, helloClass); + + OutputAnalyzer out = CDSTestUtils.executeAndLog(pb, "train"); + out.shouldContain("Hello World"); + out.shouldHaveExitValue(0); + + // (2) Assembly Phase + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTMode=create", + "-XX:AOTConfiguration=" + aotConfigFile, + "-XX:AOTCache=" + aotCacheFile, + "-Xlog:cds", + "-cp", appJar); + out = CDSTestUtils.executeAndLog(pb, "asm"); + out.shouldContain("Dumping shared data to file:"); + out.shouldMatch("cds.*hello[.]aot"); + out.shouldHaveExitValue(0); + + // (3) Production Run with AOTCache + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTCache=" + aotCacheFile, + "-Xlog:cds", + "-cp", appJar, helloClass); + out = CDSTestUtils.executeAndLog(pb, "prod"); + out.shouldContain("Opened archive hello.aot."); + out.shouldContain("Hello World"); + out.shouldHaveExitValue(0); + + // (4) AOTMode=off + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTCache=" + aotCacheFile, + "--show-version", + "-Xlog:cds", + "-XX:AOTMode=off", + "-cp", appJar, helloClass); + out = CDSTestUtils.executeAndLog(pb, "prod"); + out.shouldNotContain(", sharing"); + out.shouldNotContain("Opened archive hello.aot."); + out.shouldContain("Hello World"); + out.shouldHaveExitValue(0); + + // (5) AOTMode=auto + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTCache=" + aotCacheFile, + "--show-version", + "-Xlog:cds", + "-XX:AOTMode=auto", + "-cp", appJar, helloClass); + out = CDSTestUtils.executeAndLog(pb, "prod"); + out.shouldContain(", sharing"); + out.shouldContain("Opened archive hello.aot."); + out.shouldContain("Hello World"); + out.shouldHaveExitValue(0); + + // (5) AOTMode=on + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTCache=" + aotCacheFile, + "--show-version", + "-Xlog:cds", + "-XX:AOTMode=on", + "-cp", appJar, helloClass); + out = CDSTestUtils.executeAndLog(pb, "prod"); + out.shouldContain(", sharing"); + out.shouldContain("Opened archive hello.aot."); + out.shouldContain("Hello World"); + out.shouldHaveExitValue(0); + } + + static void negativeTests() throws Exception { + // (1) Mixing old and new options + String mixOldNewErrSuffix = " cannot be used at the same time with -Xshare:on, -Xshare:auto, " + + "-Xshare:off, -Xshare:dump, DumpLoadedClassList, SharedClassListFile, " + + "or SharedArchiveFile"; + + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xshare:off", + "-XX:AOTConfiguration=" + aotConfigFile, + "-cp", appJar, helloClass); + + OutputAnalyzer out = CDSTestUtils.executeAndLog(pb, "neg"); + out.shouldContain("Option AOTConfiguration" + mixOldNewErrSuffix); + out.shouldNotHaveExitValue(0); + + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:SharedArchiveFile=" + aotCacheFile, + "-XX:AOTCache=" + aotCacheFile, + "-cp", appJar, helloClass); + out = CDSTestUtils.executeAndLog(pb, "neg"); + out.shouldContain("Option AOTCache" + mixOldNewErrSuffix); + out.shouldNotHaveExitValue(0); + + // (2) Use AOTConfiguration without AOTMode + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTConfiguration=" + aotConfigFile, + "-cp", appJar, helloClass); + + out = CDSTestUtils.executeAndLog(pb, "neg"); + out.shouldContain("AOTConfiguration can only be used with -XX:AOTMode=record or -XX:AOTMode=create"); + out.shouldNotHaveExitValue(0); + + // (3) Use AOTMode without AOTConfiguration + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTMode=record", + "-cp", appJar, helloClass); + + out = CDSTestUtils.executeAndLog(pb, "neg"); + out.shouldContain("-XX:AOTMode=record cannot be used without setting AOTConfiguration"); + + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTMode=create", + "-cp", appJar, helloClass); + + out = CDSTestUtils.executeAndLog(pb, "neg"); + out.shouldContain("-XX:AOTMode=create cannot be used without setting AOTConfiguration"); + out.shouldNotHaveExitValue(0); + + // (4) Bad AOTMode + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTMode=foo", + "-cp", appJar, helloClass); + + out = CDSTestUtils.executeAndLog(pb, "neg"); + out.shouldContain("Unrecognized value foo for AOTMode. Must be one of the following: off, record, create, auto, on"); + out.shouldNotHaveExitValue(0); + + // (5) AOTCache specified with -XX:AOTMode=record + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTMode=record", + "-XX:AOTConfiguration=" + aotConfigFile, + "-XX:AOTCache=" + aotCacheFile, + "-cp", appJar, helloClass); + + out = CDSTestUtils.executeAndLog(pb, "neg"); + out.shouldContain("AOTCache must not be specified when using -XX:AOTMode=record"); + out.shouldNotHaveExitValue(0); + + // (5) AOTCache not specified with -XX:AOTMode=create + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTMode=create", + "-XX:AOTConfiguration=" + aotConfigFile, + "-cp", appJar, helloClass); + + out = CDSTestUtils.executeAndLog(pb, "neg"); + out.shouldContain("AOTCache must be specified when using -XX:AOTMode=create"); + out.shouldNotHaveExitValue(0); + + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/ClassPathAttr.java b/test/hotspot/jtreg/runtime/cds/appcds/ClassPathAttr.java index fe618256f65..df47e6bebc8 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/ClassPathAttr.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/ClassPathAttr.java @@ -190,13 +190,20 @@ public class ClassPathAttr { Files.copy(Paths.get(cp), Paths.get(nonExistPath), StandardCopyOption.REPLACE_EXISTING); - TestCommon.run( + CDSTestUtils.Result result = TestCommon.run( "-Xlog:class+path", "-cp", cp, - "CpAttr6") - .assertNormalExit(output -> { - output.shouldMatch("Archived non-system classes are disabled because the file .*cpattrX.jar exists"); - }); + "CpAttr6"); + if (CDSTestUtils.isAOTClassLinkingEnabled()) { + result.assertAbnormalExit(output -> { + output.shouldMatch("CDS archive has aot-linked classes. It cannot be used because the file .*cpattrX.jar exists"); + }); + + } else { + result.assertNormalExit(output -> { + output.shouldMatch("Archived non-system classes are disabled because the file .*cpattrX.jar exists"); + }); + } } static void testClassPathAttrJarOnCP() throws Exception { diff --git a/test/hotspot/jtreg/runtime/cds/appcds/LambdaContainsOldInf.java b/test/hotspot/jtreg/runtime/cds/appcds/LambdaContainsOldInf.java index 8dec80b9f58..6d1b23628c5 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/LambdaContainsOldInf.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/LambdaContainsOldInf.java @@ -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 @@ -64,7 +64,11 @@ public class LambdaContainsOldInf { OutputAnalyzer output = CDSTestUtils.createArchiveAndCheck(opts); TestCommon.checkExecReturn(output, 0, true, "Skipping OldProvider: Old class has been linked"); - output.shouldMatch("Skipping.LambdaContainsOldInfApp[$][$]Lambda.*0x.*:.*Old.class.has.been.linked"); + if (CDSTestUtils.isAOTClassLinkingEnabled()) { + output.shouldMatch("Cannot aot-resolve Lambda proxy because OldProvider is excluded"); + } else { + output.shouldMatch("Skipping.LambdaContainsOldInfApp[$][$]Lambda.*0x.*:.*Old.class.has.been.linked"); + } // run with archive CDSOptions runOpts = (new CDSOptions()) diff --git a/test/hotspot/jtreg/runtime/cds/appcds/LambdaWithOldClass.java b/test/hotspot/jtreg/runtime/cds/appcds/LambdaWithOldClass.java index 3a0bb740387..d7ce22e3cf1 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/LambdaWithOldClass.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/LambdaWithOldClass.java @@ -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 @@ -68,7 +68,11 @@ public class LambdaWithOldClass { .addSuffix(mainClass); OutputAnalyzer output = CDSTestUtils.runWithArchive(runOpts); output.shouldContain("[class,load] LambdaWithOldClassApp source: shared objects file") - .shouldMatch(".class.load. LambdaWithOldClassApp[$][$]Lambda.*/0x.*source:.*shared objects file") .shouldHaveExitValue(0); + if (!CDSTestUtils.isAOTClassLinkingEnabled()) { + // With AOTClassLinking, we don't archive any lambda with old classes in the method + // signatures. + output.shouldMatch(".class.load. LambdaWithOldClassApp[$][$]Lambda.*/0x.*source:.*shared objects file"); + } } } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/LambdaWithUseImplMethodHandle.java b/test/hotspot/jtreg/runtime/cds/appcds/LambdaWithUseImplMethodHandle.java index 513ec57d279..360fce5879a 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/LambdaWithUseImplMethodHandle.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/LambdaWithUseImplMethodHandle.java @@ -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 @@ -43,6 +43,11 @@ public class LambdaWithUseImplMethodHandle { // See pkg2/Child.jcod for details about the condition that triggers JDK-8290417 public static void main(String[] args) throws Exception { + test(false); + test(true); + } + + static void test(boolean aotClassLinking) throws Exception { String appJar = ClassFileInstaller.getJarPath("test.jar"); String mainClass = "LambdaWithUseImplMethodHandleApp"; String expectedMsg = "Called BaseWithProtectedMethod::protectedMethod"; @@ -57,6 +62,9 @@ public class LambdaWithUseImplMethodHandle { .addPrefix("-XX:ExtraSharedClassListFile=" + classList, "-cp", appJar) .setArchiveName(archiveName); + if (aotClassLinking) { + opts.addPrefix("-XX:+AOTClassLinking"); + } CDSTestUtils.createArchiveAndCheck(opts); // run with archive diff --git a/test/hotspot/jtreg/runtime/cds/appcds/TestParallelGCWithCDS.java b/test/hotspot/jtreg/runtime/cds/appcds/TestParallelGCWithCDS.java index 705df07f841..781e0276b27 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/TestParallelGCWithCDS.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/TestParallelGCWithCDS.java @@ -148,6 +148,7 @@ public class TestParallelGCWithCDS { } else { String pattern = "((Too small maximum heap)" + "|(GC triggered before VM initialization completed)" + + "|(CDS archive has aot-linked classes but the archived heap objects cannot be loaded)" + "|(Initial heap size set to a larger value than the maximum heap size)" + "|(java.lang.OutOfMemoryError)" + "|(Error: A JNI error has occurred, please check your installation and try again))"; diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTClassLinkingVMOptions.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTClassLinkingVMOptions.java new file mode 100644 index 00000000000..05fdf7c06d6 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/AOTClassLinkingVMOptions.java @@ -0,0 +1,183 @@ +/* + * 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 + * @requires vm.cds + * @requires vm.cds.supports.aot.class.linking + * @requires vm.flagless + * @summary Disable CDS when incompatible options related to AOTClassLinking are used + * @library /test/jdk/lib/testlibrary + * /test/lib + * /test/hotspot/jtreg/runtime/cds/appcds + * /test/hotspot/jtreg/runtime/cds/appcds/test-classes + * @build Hello + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar Hello + * @run driver AOTClassLinkingVMOptions + */ + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import jdk.test.lib.cds.CDSTestUtils; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class AOTClassLinkingVMOptions { + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + + static int testCaseNum = 0; + static void testCase(String s) { + testCaseNum++; + System.out.println("Test case " + testCaseNum + ": " + s); + } + + public static void main(String[] args) throws Exception { + TestCommon.testDump(appJar, TestCommon.list("Hello"), + "-XX:+AOTClassLinking"); + + testCase("Archived full module graph must be enabled at runtime"); + TestCommon.run("-cp", appJar, "-Djdk.module.validation=1", "Hello") + .assertAbnormalExit("CDS archive has aot-linked classes." + + " It cannot be used when archived full module graph is not used"); + + testCase("Cannot use -Djava.system.class.loader"); + TestCommon.run("-cp", appJar, "-Djava.system.class.loader=dummy", "Hello") + .assertAbnormalExit("CDS archive has aot-linked classes." + + " It cannot be used when the java.system.class.loader property is specified."); + + testCase("Cannot use a different main module"); + TestCommon.run("-cp", appJar, "-Xlog:cds", "-m", "jdk.compiler/com.sun.tools.javac.Main") + .assertAbnormalExit("CDS archive has aot-linked classes." + + " It cannot be used when archived full module graph is not used."); + testCase("Cannot use security manager"); + TestCommon.run("-cp", appJar, "-Xlog:cds", "-Djava.security.manager=allow") + .assertAbnormalExit("CDS archive has aot-linked classes." + + " It cannot be used with -Djava.security.manager=allow."); + TestCommon.run("-cp", appJar, "-Xlog:cds", "-Djava.security.manager=default") + .assertAbnormalExit("CDS archive has aot-linked classes." + + " It cannot be used with -Djava.security.manager=default."); + + // NOTE: tests for ClassFileLoadHook + AOTClassLinking is in + // ../jvmti/ClassFileLoadHookTest.java + + boolean dynamicMode = Boolean.getBoolean("test.dynamic.cds.archive"); + if (!dynamicMode) { + // These tests need to dump the full module graph, which is not possible with + // dynamic dump. + modulePathTests(); + } + } + + static void modulePathTests() throws Exception { + CDSModulePathUtils.init(); + + testCase("Cannot use mis-matched module path"); + String goodModulePath = CDSModulePathUtils.getModulesDir().toString(); + TestCommon.testDump(null, CDSModulePathUtils.getAppClasses(), + "--module-path", goodModulePath, + "-XX:+AOTClassLinking", + "-m", CDSModulePathUtils.MAIN_MODULE); + TestCommon.run("-Xlog:cds", + "--module-path", goodModulePath, + "-m", CDSModulePathUtils.MAIN_MODULE) + .assertNormalExit("Using AOT-linked classes: true"); + + TestCommon.run("-Xlog:cds", + "--module-path", goodModulePath + "/bad", + "-m", CDSModulePathUtils.MAIN_MODULE) + .assertAbnormalExit("CDS archive has aot-linked classes. It cannot be used when archived full module graph is not used."); + + testCase("Cannot use mis-matched --add-modules"); + TestCommon.testDump(null, CDSModulePathUtils.getAppClasses(), + "--module-path", goodModulePath, + "-XX:+AOTClassLinking", + "--add-modules", CDSModulePathUtils.MAIN_MODULE); + TestCommon.run("-Xlog:cds", + "--module-path", goodModulePath, + "--add-modules", CDSModulePathUtils.MAIN_MODULE, + CDSModulePathUtils.MAIN_CLASS) + .assertNormalExit("Using AOT-linked classes: true"); + + TestCommon.run("-Xlog:cds", + "--module-path", goodModulePath + "/bad", + "--add-modules", CDSModulePathUtils.TEST_MODULE, + CDSModulePathUtils.MAIN_CLASS) + .assertAbnormalExit("Mismatched --add-modules module name(s)", + "CDS archive has aot-linked classes. It cannot be used when archived full module graph is not used."); + } +} + +// TODO: enhance and move this class to jdk.test.lib.cds.CDSModulePathUtils + +class CDSModulePathUtils { + private static String TEST_SRC = System.getProperty("test.root"); + private static Path USER_DIR = Paths.get(CDSTestUtils.getOutputDir()); + private static Path SRC_DIR = Paths.get(TEST_SRC, "runtime/cds/appcds/jigsaw/modulepath/src"); + private static Path MODS_DIR = Paths.get("mods"); + + public static String MAIN_MODULE = "com.bars"; + public static String TEST_MODULE = "com.foos"; + + public static String MAIN_CLASS = "com.bars.Main"; + public static String TEST_CLASS = "com.foos.Test"; + private static String appClasses[] = {MAIN_CLASS, TEST_CLASS}; + + private static Path modulesDir; + + // This directory contains all the modular jar files + // $USER_DIR/modules/com.bars.jar + // $USER_DIR/modules/com.foos.jar + static Path getModulesDir() { + return modulesDir; + } + + static String[] getAppClasses() { + return appClasses; + } + + static void init() throws Exception { + JarBuilder.compileModule(SRC_DIR.resolve(TEST_MODULE), + MODS_DIR.resolve(TEST_MODULE), + null); + JarBuilder.compileModule(SRC_DIR.resolve(MAIN_MODULE), + MODS_DIR.resolve(MAIN_MODULE), + MODS_DIR.toString()); + + String PATH_LIBS = "modules"; + modulesDir = Files.createTempDirectory(USER_DIR, PATH_LIBS); + Path mainJar = modulesDir.resolve(MAIN_MODULE + ".jar"); + Path testJar = modulesDir.resolve(TEST_MODULE + ".jar"); + + // modylibs contains both modules com.foos.jar, com.bars.jar + // build com.foos.jar + String classes = MODS_DIR.resolve(TEST_MODULE).toString(); + JarBuilder.createModularJar(testJar.toString(), classes, TEST_CLASS); + + // build com.bars.jar + classes = MODS_DIR.resolve(MAIN_MODULE).toString(); + JarBuilder.createModularJar(mainJar.toString(), classes, MAIN_CLASS); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java new file mode 100644 index 00000000000..49544f50032 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/BulkLoaderTest.java @@ -0,0 +1,241 @@ +/* + * 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. + * + */ + +// AOT-linked classes are loaded during VM bootstrap by the C++ class AOTLinkedClassBulkLoader. +// Make sure that the Module, Package, CodeSource and ProtectionDomain of these classes are +// set up properly. + +/* + * @test id=static + * @requires vm.cds.supports.aot.class.linking + * @library /test/jdk/lib/testlibrary /test/lib + * @build InitiatingLoaderTester + * @build BulkLoaderTest + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar BulkLoaderTestApp.jar BulkLoaderTestApp MyUtil InitiatingLoaderTester + * @run driver BulkLoaderTest STATIC + */ + +/* + * @test id=dynamic + * @requires vm.cds.supports.aot.class.linking + * @library /test/jdk/lib/testlibrary /test/lib + * @build InitiatingLoaderTester + * @build BulkLoaderTest + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar BulkLoaderTestApp.jar BulkLoaderTestApp MyUtil InitiatingLoaderTester + * @run driver BulkLoaderTest DYNAMIC + */ + +import java.io.File; +import java.lang.StackWalker.StackFrame; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.Set; +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; + +public class BulkLoaderTest { + static final String appJar = ClassFileInstaller.getJarPath("BulkLoaderTestApp.jar"); + static final String mainClass = "BulkLoaderTestApp"; + + public static void main(String[] args) throws Exception { + Tester t = new Tester(); + + // Run with archived FMG loaded + t.run(args); + + // Run with an extra classpath -- archived FMG can still load. + { + String extraVmArgs[] = { + "-cp", + appJar + File.pathSeparator + "foobar.jar" + }; + OutputAnalyzer out = t.productionRun(extraVmArgs); + out.shouldHaveExitValue(0); + } + + // Run without archived FMG -- fail to load + { + String extraVmArgs[] = { + "-Xshare:on", + "-Xlog:cds", + "-Djdk.module.showModuleResolution=true" + }; + t.setCheckExitValue(false); + OutputAnalyzer out = t.productionRun(extraVmArgs); + out.shouldHaveExitValue(1); + out.shouldContain("CDS archive has aot-linked classes. It cannot be used when archived full module graph is not used."); + t.setCheckExitValue(true); + } + } + + static class Tester extends CDSAppTester { + public Tester() { + super(mainClass); + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] vmArgs(RunMode runMode) { + return new String[] { + "-Xlog:cds,cds+aot+load", + "-XX:+AOTClassLinking", + }; + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] { + mainClass, + }; + } + } +} + +class BulkLoaderTestApp { + static String allPerms = "null.*.*java.security.Permissions.*,*java.security.AllPermission.*.*"; + + public static void main(String args[]) throws Exception { + checkClasses(); + checkInitiatingLoader(); + } + + // Check the ClassLoader/Module/Package/ProtectionDomain/CodeSource of classes that are aot-linked + static void checkClasses() throws Exception { + check(String.class, + "null", // loader + "module java.base", + "package java.lang", + "null", + allPerms); + + check(Class.forName("sun.util.logging.internal.LoggingProviderImpl"), + "null", + "module java.logging", + "package sun.util.logging.internal", + "null", + allPerms); + + + check(javax.tools.FileObject.class, + "^jdk.internal.loader.ClassLoaders[$]PlatformClassLoader@", + "module java.compiler", + "package javax.tools", + "jrt:/java.compiler ", + "jdk.internal.loader.ClassLoaders[$]PlatformClassLoader.*.*java.security.Permissions"); + + check(BulkLoaderTestApp.class, + "jdk.internal.loader.ClassLoaders[$]AppClassLoader@", + "^unnamed module @", + "package ", + "file:.*BulkLoaderTestApp.jar ", + "jdk.internal.loader.ClassLoaders[$]AppClassLoader.*.*java.security.Permissions"); + + check(Class.forName("com.sun.tools.javac.Main"), + "jdk.internal.loader.ClassLoaders[$]AppClassLoader@", + "module jdk.compiler", + "package com.sun.tools.javac", + "jrt:/jdk.compiler ", + "jdk.internal.loader.ClassLoaders[$]AppClassLoader.*.*java.security.Permissions"); + + doit(() -> { + Class lambdaClass = MyUtil.getCallerClass(1); + check(lambdaClass, + "jdk.internal.loader.ClassLoaders[$]AppClassLoader@", + "unnamed module", + "package ", + "file:.*BulkLoaderTestApp.jar ", + "jdk.internal.loader.ClassLoaders[$]AppClassLoader.*.*java.security.Permissions"); + + }); + } + + static void check(Class c, String loader, String module, String pkg, String codeSource, String protectionDomain) { + System.out.println("===================================================================="); + System.out.println(c.getName() + ", loader = " + c.getClassLoader()); + System.out.println(c.getName() + ", module = " + c.getModule()); + System.out.println(c.getName() + ", package = " + c.getPackage()); + System.out.println(c.getName() + ", CS = " + c.getProtectionDomain().getCodeSource()); + System.out.println(c.getName() + ", PD = " + c.getProtectionDomain()); + + expectMatch("" + c.getClassLoader(), loader); + expectMatch("" + c.getModule(), module); + expectSame("" + c.getPackage(), pkg); + expectMatch("" + c.getProtectionDomain().getCodeSource(), codeSource); + expectMatch("" + c.getProtectionDomain(), protectionDomain); + } + + static void expectSame(String a, String b) { + if (!a.equals(b)) { + throw new RuntimeException("Expected \"" + b + "\" but got \"" + a + "\""); + } + } + static void expectMatch(String string, String pattern) { + Matcher matcher = Pattern.compile(pattern, Pattern.DOTALL).matcher(string); + if (!matcher.find()) { + throw new RuntimeException("Expected pattern \"" + pattern + "\" but got \"" + string + "\""); + } + } + + static void doit(Runnable t) { + t.run(); + } + + static void checkInitiatingLoader() throws Exception { + try { + InitiatingLoaderTester.tryAccess(); + } catch (IllegalAccessError t) { + if (t.getMessage().contains("cannot access class jdk.internal.misc.Unsafe (in module java.base)")) { + System.out.println("Expected exception:"); + t.printStackTrace(System.out); + // Class.forName() should still work. We just can't resolve it in CP entries. + Class c = Class.forName("jdk.internal.misc.Unsafe"); + System.out.println("App loader can still resolve by name: " + c); + return; + } + throw new RuntimeException("Unexpected exception", t); + } + + throw new RuntimeException("Should not have succeeded"); + } +} + +class MyUtil { + // depth is 0-based -- i.e., depth==0 returns the class of the immediate caller of getCallerClass + static Class getCallerClass(int depth) { + // Need to add the frame of the getCallerClass -- so the immediate caller (depth==0) of this method + // is at stack.get(1) == stack.get(depth+1); + StackWalker walker = StackWalker.getInstance( + Set.of(StackWalker.Option.RETAIN_CLASS_REFERENCE, + StackWalker.Option.SHOW_HIDDEN_FRAMES)); + List stack = walker.walk(s -> s.limit(depth+2).collect(Collectors.toList())); + return stack.get(depth+1).getDeclaringClass(); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/InitiatingLoaderTester.jasm b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/InitiatingLoaderTester.jasm new file mode 100644 index 00000000000..ae1b38e20a9 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/InitiatingLoaderTester.jasm @@ -0,0 +1,56 @@ +/* + * 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 InitiatingLoaderTester { + public static Object tryAccess() { + return jdk.internal.misc.Unsafe.getUnsafe(); + } +} + + + +*/ + + +super class InitiatingLoaderTester + version 66:0 +{ + Method "":"()V" + stack 1 locals 1 + { + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + public static Method tryAccess:"()Ljava/lang/Object;" + stack 2 locals 0 + { + invokestatic Method jdk/internal/misc/Unsafe."getUnsafe":"()Ljdk/internal/misc/Unsafe;"; + areturn; + } + +} // end Class InitiatingLoaderTester diff --git a/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/ArchiveHeapTestClass.java b/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/ArchiveHeapTestClass.java index f5975569447..1603d8430b2 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/ArchiveHeapTestClass.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/ArchiveHeapTestClass.java @@ -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 @@ -27,7 +27,7 @@ * @bug 8214781 8293187 * @summary Test for the -XX:ArchiveHeapTestClass flag * @requires vm.debug == true & vm.cds.write.archived.java.heap - * @modules java.base/sun.invoke.util java.logging + * @modules java.logging * @library /test/jdk/lib/testlibrary /test/lib * /test/hotspot/jtreg/runtime/cds/appcds * /test/hotspot/jtreg/runtime/cds/appcds/test-classes @@ -35,12 +35,13 @@ * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar boot.jar * CDSTestClassA CDSTestClassA$XX CDSTestClassA$YY * CDSTestClassB CDSTestClassC CDSTestClassD - * CDSTestClassE CDSTestClassF CDSTestClassG + * CDSTestClassE CDSTestClassF CDSTestClassG CDSTestClassG$MyEnum CDSTestClassG$Wrapper * pkg.ClassInPackage * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar Hello * @run driver ArchiveHeapTestClass */ +import jdk.test.lib.cds.CDSTestUtils; import jdk.test.lib.Platform; import jdk.test.lib.helpers.ClassFileInstaller; import jdk.test.lib.process.OutputAnalyzer; @@ -151,19 +152,24 @@ public class ArchiveHeapTestClass { output = dumpBootAndHello(CDSTestClassD_name); mustFail(output, "Unable to find the static T_OBJECT field CDSTestClassD::archivedObjects"); - testCase("Use a disallowed class: in unnamed module but not in unname package"); - output = dumpBootAndHello(CDSTestClassE_name); - mustFail(output, "Class pkg.ClassInPackage not allowed in archive heap"); + if (!CDSTestUtils.isAOTClassLinkingEnabled()) { + testCase("Use a disallowed class: in unnamed module but not in unname package"); + output = dumpBootAndHello(CDSTestClassE_name); + mustFail(output, "Class pkg.ClassInPackage not allowed in archive heap"); - testCase("Use a disallowed class: not in java.base module"); - output = dumpBootAndHello(CDSTestClassF_name); - mustFail(output, "Class java.util.logging.Level not allowed in archive heap"); - - if (false) { // JDK-8293187 - testCase("sun.invoke.util.Wrapper"); - output = dumpBootAndHello(CDSTestClassG_name); - mustSucceed(output); + testCase("Use a disallowed class: not in java.base module"); + output = dumpBootAndHello(CDSTestClassF_name); + mustFail(output, "Class java.util.logging.Level not allowed in archive heap"); } + + testCase("Complex enums"); + output = dumpBootAndHello(CDSTestClassG_name, "-XX:+AOTClassLinking", "-Xlog:cds+class=debug"); + mustSucceed(output); + + TestCommon.run("-Xbootclasspath/a:" + bootJar, "-cp", appJar, "-Xlog:cds+heap,cds+init", + CDSTestClassG_name) + .assertNormalExit("init subgraph " + CDSTestClassG_name, + "Initialized from CDS"); } } @@ -171,12 +177,27 @@ class CDSTestClassA { static final String output = "CDSTestClassA. was executed"; static Object[] archivedObjects; static { - archivedObjects = new Object[5]; - archivedObjects[0] = output; - archivedObjects[1] = new CDSTestClassA[0]; - archivedObjects[2] = new YY(); - archivedObjects[3] = new int[0]; - archivedObjects[4] = new int[2][2]; + // The usual convention would be to call this here: + // CDS.initializeFromArchive(CDSTestClassA.class); + // However, the CDS class is not exported to the unnamed module by default, + // and we don't want to use "--add-exports java.base/jdk.internal.misc=ALL-UNNAMED", as + // that would disable the archived full module graph, which will disable + // CDSConfig::is_using_aot_linked_classes(). + // + // Instead, HeapShared::initialize_test_class_from_archive() will set up the + // "archivedObjects" field first, before calling CDSTestClassA.. So + // if we see that archivedObjects is magically non-null here, that means + // it has been restored from the CDS archive. + if (archivedObjects == null) { + archivedObjects = new Object[5]; + archivedObjects[0] = output; + archivedObjects[1] = new CDSTestClassA[0]; + archivedObjects[2] = new YY(); + archivedObjects[3] = new int[0]; + archivedObjects[4] = new int[2][2]; + } else { + System.out.println("Initialized from CDS"); + } System.out.println(output); System.out.println("CDSTestClassA module = " + CDSTestClassA.class.getModule()); System.out.println("CDSTestClassA package = " + CDSTestClassA.class.getPackage()); @@ -269,8 +290,143 @@ class CDSTestClassF { class CDSTestClassG { static Object[] archivedObjects; static { - // Not in java.base - archivedObjects = new Object[1]; - archivedObjects[0] = sun.invoke.util.Wrapper.BOOLEAN; + if (archivedObjects == null) { + archivedObjects = new Object[13]; + archivedObjects[0] = Wrapper.BOOLEAN; + archivedObjects[1] = Wrapper.INT.zero(); + archivedObjects[2] = Wrapper.DOUBLE.zero(); + archivedObjects[3] = MyEnum.DUMMY1; + + archivedObjects[4] = Boolean.class; + archivedObjects[5] = Byte.class; + archivedObjects[6] = Character.class; + archivedObjects[7] = Short.class; + archivedObjects[8] = Integer.class; + archivedObjects[9] = Long.class; + archivedObjects[10] = Float.class; + archivedObjects[11] = Double.class; + archivedObjects[12] = Void.class; + } else { + System.out.println("Initialized from CDS"); + } + } + + public static void main(String args[]) { + if (archivedObjects[0] != Wrapper.BOOLEAN) { + throw new RuntimeException("Huh 0"); + } + + if (archivedObjects[1] != Wrapper.INT.zero()) { + throw new RuntimeException("Huh 1"); + } + + if (archivedObjects[2] != Wrapper.DOUBLE.zero()) { + throw new RuntimeException("Huh 2"); + } + + if (archivedObjects[3] != MyEnum.DUMMY1) { + throw new RuntimeException("Huh 3"); + } + + if (MyEnum.BOOLEAN != true) { + throw new RuntimeException("Huh 10.1"); + } + if (MyEnum.BYTE != -128) { + throw new RuntimeException("Huh 10.2"); + } + if (MyEnum.CHAR != 'c') { + throw new RuntimeException("Huh 10.3"); + } + if (MyEnum.SHORT != -12345) { + throw new RuntimeException("Huh 10.4"); + } + if (MyEnum.INT != -123456) { + throw new RuntimeException("Huh 10.5"); + } + if (MyEnum.LONG != 0x1234567890L) { + throw new RuntimeException("Huh 10.6"); + } + if (MyEnum.LONG2 != -0x1234567890L) { + throw new RuntimeException("Huh 10.7"); + } + if (MyEnum.FLOAT != 567891.0f) { + throw new RuntimeException("Huh 10.8"); + } + if (MyEnum.DOUBLE != 12345678905678.890) { + throw new RuntimeException("Huh 10.9"); + } + + checkClass(4, Boolean.class); + checkClass(5, Byte.class); + checkClass(6, Character.class); + checkClass(7, Short.class); + checkClass(8, Integer.class); + checkClass(9, Long.class); + checkClass(10, Float.class); + checkClass(11, Double.class); + checkClass(12, Void.class); + + System.out.println("Success!"); + } + + static void checkClass(int index, Class c) { + if (archivedObjects[index] != c) { + throw new RuntimeException("archivedObjects[" + index + "] should be " + c); + } + } + + // Simplified version of sun.invoke.util.Wrapper + public enum Wrapper { + // wrapperType simple primitiveType simple char emptyArray + BOOLEAN( Boolean.class, "Boolean", boolean.class, "boolean", 'Z', new boolean[0]), + INT ( Integer.class, "Integer", int.class, "int", 'I', new int[0]), + DOUBLE ( Double.class, "Double", double.class, "double", 'D', new double[0]) + ; + + public static final int COUNT = 10; + private static final Object DOUBLE_ZERO = (Double)(double)0; + + private final Class wrapperType; + private final Class primitiveType; + private final char basicTypeChar; + private final String basicTypeString; + private final Object emptyArray; + + Wrapper(Class wtype, + String wtypeName, + Class ptype, + String ptypeName, + char tchar, + Object emptyArray) { + this.wrapperType = wtype; + this.primitiveType = ptype; + this.basicTypeChar = tchar; + this.basicTypeString = String.valueOf(this.basicTypeChar); + this.emptyArray = emptyArray; + } + + public Object zero() { + return switch (this) { + case BOOLEAN -> Boolean.FALSE; + case INT -> (Integer)0; + case DOUBLE -> DOUBLE_ZERO; + default -> null; + }; + } + } + + enum MyEnum { + DUMMY1, + DUMMY2; + + static final boolean BOOLEAN = true; + static final byte BYTE = -128; + static final short SHORT = -12345; + static final char CHAR = 'c'; + static final int INT = -123456; + static final long LONG = 0x1234567890L; + static final long LONG2 = -0x1234567890L; + static final float FLOAT = 567891.0f; + static final double DOUBLE = 12345678905678.890; } } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/customLoader/CustomClassListDump.java b/test/hotspot/jtreg/runtime/cds/appcds/customLoader/CustomClassListDump.java index 625ef1fbc92..27072bea35e 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/customLoader/CustomClassListDump.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/customLoader/CustomClassListDump.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 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 @@ -94,7 +94,7 @@ public class CustomClassListDump { .shouldContain("unreg CustomLoadee") .shouldContain("unreg CustomLoadee2") .shouldContain("unreg CustomLoadee3Child") - .shouldContain("unreg OldClass ** unlinked"); + .shouldContain("unreg OldClass old unlinked"); // Use the dumped static archive opts = (new CDSOptions()) diff --git a/test/hotspot/jtreg/runtime/cds/appcds/jvmti/ClassFileLoadHookTest.java b/test/hotspot/jtreg/runtime/cds/appcds/jvmti/ClassFileLoadHookTest.java index 5751f62e148..7ccd4e5d312 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/jvmti/ClassFileLoadHookTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/jvmti/ClassFileLoadHookTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -96,5 +96,23 @@ public class ClassFileLoadHookTest { "ClassFileLoadHook", "" + ClassFileLoadHook.TestCaseId.SHARING_ON_CFLH_ON); TestCommon.checkExec(out); + + // JEP 483: if dumped with -XX:+AOTClassLinking, cannot use archive when CFLH is enabled + TestCommon.testDump(appJar, sharedClasses, useWb, "-XX:+AOTClassLinking"); + out = TestCommon.exec(appJar, + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+WhiteBoxAPI", useWb, + "-agentlib:SimpleClassFileLoadHook=LoadMe,beforeHook,after_Hook", + "-Xlog:cds", + "ClassFileLoadHook", + "" + ClassFileLoadHook.TestCaseId.SHARING_ON_CFLH_ON); + if (out.contains("Using AOT-linked classes: false (static archive: no aot-linked classes")) { + // JTREG is executed with VM options that do not support -XX:+AOTClassLinking, so + // the static archive was not created with aot-linked classes. + out.shouldHaveExitValue(0); + } else { + out.shouldContain("CDS archive has aot-linked classes. It cannot be used when JVMTI ClassFileLoadHook is in use."); + out.shouldNotHaveExitValue(0); + } } } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/jvmti/redefineClasses/OldClassAndRedefineClass.java b/test/hotspot/jtreg/runtime/cds/appcds/jvmti/redefineClasses/OldClassAndRedefineClass.java index 8ca8b7d952e..7581b2367aa 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/jvmti/redefineClasses/OldClassAndRedefineClass.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/jvmti/redefineClasses/OldClassAndRedefineClass.java @@ -61,8 +61,8 @@ public class OldClassAndRedefineClass { String agentCmdArg = "-javaagent:redefineagent.jar"; OutputAnalyzer out = TestCommon.testDump(appJar, sharedClasses, "-Xlog:cds,cds+class=debug"); - out.shouldMatch("klasses.*OldSuper.[*][*].unlinked") - .shouldMatch("klasses.*ChildOldSuper.[*][*].unlinked"); + out.shouldMatch("klasses.*OldSuper.* unlinked") + .shouldMatch("klasses.*ChildOldSuper.* unlinked"); out = TestCommon.exec( appJar, diff --git a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java new file mode 100644 index 00000000000..5fb0a30cd61 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java @@ -0,0 +1,388 @@ +/* + * 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 AOT resolution of lambda expressions + * @bug 8340836 + * @requires vm.cds + * @requires vm.cds.supports.aot.class.linking + * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes/ + * @build AOTLinkedLambdas + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar + * AOTLinkedLambdasApp InitTracker + * IntfWithNoClinit IntfWithNoClinit2 + * IA IB IC ID1 ID2 IE1 IE2 IF1 IF2 IG1 IG2 IH1 IH2 IH3 + * FooA FooB + * BarA BarB BarC + * @run driver AOTLinkedLambdas + */ + +import java.util.function.Supplier; +import static java.util.stream.Collectors.*; +import jdk.test.lib.cds.CDSOptions; +import jdk.test.lib.cds.CDSTestUtils; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; + +public class AOTLinkedLambdas { + static final String classList = "AOTLinkedLambdas.classlist"; + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + static final String mainClass = AOTLinkedLambdasApp.class.getName(); + + public static void main(String[] args) throws Exception { + CDSTestUtils.dumpClassList(classList, "-cp", appJar, mainClass) + .assertNormalExit(output -> { + output.shouldContain("Hello AOTLinkedLambdasApp"); + }); + + CDSOptions opts = (new CDSOptions()) + .addPrefix("-XX:ExtraSharedClassListFile=" + classList, + "-XX:+AOTClassLinking", + "-Xlog:cds+resolve=trace", + "-Xlog:cds+class=debug", + "-cp", appJar); + + OutputAnalyzer dumpOut = CDSTestUtils.createArchiveAndCheck(opts); + dumpOut.shouldContain("Can aot-resolve Lambda proxy of interface type IA"); + dumpOut.shouldContain("Can aot-resolve Lambda proxy of interface type IB"); + dumpOut.shouldContain("Cannot aot-resolve Lambda proxy of interface type IC"); + dumpOut.shouldContain("Can aot-resolve Lambda proxy of interface type ID2"); + dumpOut.shouldContain("Cannot aot-resolve Lambda proxy of interface type IE2"); // unsupported = IE1 + dumpOut.shouldContain("Cannot aot-resolve Lambda proxy of interface type IF2"); + dumpOut.shouldContain("Cannot aot-resolve Lambda proxy of interface type IG2"); + dumpOut.shouldContain("Cannot aot-resolve Lambda proxy of interface type IH3"); // unsupported = IH1 + + CDSOptions runOpts = (new CDSOptions()) + .setUseVersion(false) + .addPrefix("-Xlog:cds", + "-esa", // see JDK-8340836 + "-cp", appJar) + .addSuffix(mainClass); + + CDSTestUtils.run(runOpts) + .assertNormalExit("Hello AOTLinkedLambdasApp", + "hello, world"); + } +} + +class AOTLinkedLambdasApp { + static { + System.out.println("AOTLinkedLambdasApp."); + } + public static void main(String args[]) { + System.out.println("Hello AOTLinkedLambdasApp"); + + // (1) Simple tests + var words = java.util.List.of("hello", "fuzzy", "world"); + System.out.println(words.stream().filter(w->!w.contains("u")).collect(joining(", "))); + // => hello, world + + // (2) Test for order. + testClinitOrder(); + } + + + // Check that aot-linking of lambdas does not cause to be skipped or + // otherwise executed in the wrong order. + // + // A lambda is declared to implement an interface X, but it also implicitly + // implements all super interfaces of X. + // + // For any interface IN that's implemented by a lambda, if IN has declared + // a non-abstract, non-static method (JVMS 5.5. Initialization), IN must be + // initialized before the lambda can be linked. If IN:: exists, the + // initialization of IN can have side effects. + // + // AOTConstantPoolResolver::is_indy_resolution_deterministic() excludes + // any lambda if initializing its interfaces can cause side effects. This test + // checks that such exclusions are working as expected. + // + // This test also proves that is_indy_resolution_deterministic() doen't need to check + // for all other types that are mentioned by the lambda call site, as those classes + // will not be initialized as part of linking the lambda. + static void testClinitOrder() { + /* + * An indy callsite is associated with the following MethodType and MethodHandles: + * + * https://github.com/openjdk/jdk/blob/580eb62dc097efeb51c76b095c1404106859b673/src/java.base/share/classes/java/lang/invoke/LambdaMetafactory.java#L293-L309 + * + * MethodType factoryType The expected signature of the {@code CallSite}. The + * parameter types represent the types of capture variables; + * the return type is the interface to implement. When + * used with {@code invokedynamic}, this is provided by + * the {@code NameAndType} of the {@code InvokeDynamic} + * + * MethodType interfaceMethodType Signature and return type of method to be + * implemented by the function object. + * + * MethodHandle implementation A direct method handle describing the implementation + * method which should be called (with suitable adaptation + * of argument types and return types, and with captured + * arguments prepended to the invocation arguments) at + * invocation time. + * + * MethodType dynamicMethodType The signature and return type that should + * be enforced dynamically at invocation time. + * In simple use cases this is the same as + * {@code interfaceMethodType}. + */ + + // Initial condition: no used by our Foo* and Bar* types have been called. + InitTracker.assertOrder("InitTracker"); + + //============================== + // Case (i) -- Check for types used by factoryType, interfaceMethodType and dynamicMethodType + // (Note: no tests for captured variables in factoryType yet; will be tested in case (ii)) + // factoryType = "()LIntfWithNoClinit; + // interfaceMethodType = "(LFooB;)LFooA;" + // implementation = "REF_invokeStatic AOTLinkedLambdasApp.implAB:(LBarB;)LBarA;" + // dynamicMethodType = "(LBarB;)LBarA;" + IntfWithNoClinit noclinit = AOTLinkedLambdasApp::implAB; + + // None of the Foo? and Bar? types used by the lambda should have been initialized yet, even though + // the indy callsite has been resolved now. + InitTracker.assertOrder("InitTracker"); + + BarB barB = new BarB(); + InitTracker.assertOrder("InitTracker, FooB, BarB"); + BarA barA = noclinit.doit(barB); + System.out.println(barB); + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA"); + + //============================== + // Case (ii) -- Check for types used by captured variables in factoryType + BarC barC = null; + IntfWithNoClinit2 noclinit2 = () -> { return barC.hashCode(); }; + try { + noclinit2.doit(); + throw new RuntimeException("NullPointerException should have been thrown"); + } catch (NullPointerException npe) { + // expected + } + // BarC shouldn't be initialized as no instances of it has been created. + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA"); + + + //============================== + // (IA) No default methods -- is not initialized during lambda linking. Lambda can be archived. + IA ia = () -> {}; + ia.doit(); + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA"); + System.out.println(IA._dummy); + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA"); + + //============================== + // (IB) Has default method but has not -- OK to initialize IB during lambda linking. Lambda can be archived. + IB ib = () -> {}; + ib.doit(); + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA"); + + //============================== + // (IC) Has both default method and -- cannot AOT link the lambda + IC ic = () -> {}; + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC"); + ic.doit(); + + //============================== + // ID1 - has default method, but no + // ID2 - has , but no default method + ID2 id2 = () -> {}; + id2.doit(); + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC"); + System.out.println(ID2._dummy); + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC, ID2"); + + //============================== + // IE1 - has both default method and + // IE2 - has , but no default method + IE2 ie2 = () -> {}; + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC, ID2, IE1"); + System.out.println(IE2._dummy); + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC, ID2, IE1, IE2"); + + //============================== + // IF1 - has , but no default method + // IF2 - has both default method and + IF2 if2 = () -> {}; + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC, ID2, IE1, IE2, IF2"); + System.out.println(IF1._dummy); + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC, ID2, IE1, IE2, IF2, IF1"); + + //============================== + // IG1 - has both default method and + // IG2 - has both default method and + IG2 ig2 = () -> {}; + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC, ID2, IE1, IE2, IF2, IF1, IG1, IG2"); + + //============================== + // Similar to IE1/IE2, but IH3 is one more level away from IH1 + // IH1 - has both default method and + // IH2 - has , but no default method + // IH3 - has , but no default method + IH3 ih3 = () -> {}; + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC, ID2, IE1, IE2, IF2, IF1, IG1, IG2, IH1"); + System.out.println(IH3._dummy); + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC, ID2, IE1, IE2, IF2, IF1, IG1, IG2, IH1, IH3"); + System.out.println(IH2._dummy); + InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC, ID2, IE1, IE2, IF2, IF1, IG1, IG2, IH1, IH3, IH2"); + } + + static BarA implAB(BarB param) { + return new BarA(param); + } +} + + +// An interface with no method. A lambda implementing this +// interface can be AOT-linked. +@FunctionalInterface +interface IntfWithNoClinit { + X doit(Y param); +} + +// Another interface with no method. A lambda implementing this +// interface can be AOT-linked. +@FunctionalInterface +interface IntfWithNoClinit2 { + int doit(); +} + + +// (IA) No default methods -- is not initialized during lambda linking. Lambda can be archived. +@FunctionalInterface interface IA { + static int _dummy = InitTracker.trackEvent("IA"); + void doit(); +} + +// (IB) Has default method but has not -- OK to initialize IB during lambda linking. Lambda can be archived. +@FunctionalInterface interface IB { + default int dummy() { return 0; } + void doit(); +} + +// (IC) Has both default method and -- cannot AOT link the lambda +@FunctionalInterface interface IC { + static int _dummy = InitTracker.trackEvent("IC"); + default int dummy() { return _dummy; } + void doit(); +} + +// (ID1/ID2) +@FunctionalInterface interface ID1 { // has default method, but no + default int dummy() { return 0; } + void doit(); +} + +@FunctionalInterface interface ID2 extends ID1 { // has , but no default method + static int _dummy = InitTracker.trackEvent("ID2"); +} + +// (IE1/IE2) +@FunctionalInterface interface IE1 { // has default method and + static int _dummy = InitTracker.trackEvent("IE1"); + default int dummy() { return _dummy; } + void doit(); +} + +@FunctionalInterface interface IE2 extends IE1 { // has , but no default method + static int _dummy = InitTracker.trackEvent("IE2"); +} + +// (IF1/IF2) +@FunctionalInterface interface IF1 { // has , but no default method + static int _dummy = InitTracker.trackEvent("IF1"); + void doit(); +} + +@FunctionalInterface interface IF2 extends IF1 { // has default method and + static int _dummy = InitTracker.trackEvent("IF2"); + default int dummy() { return 0; } +} + +// (IG1/IG2) +@FunctionalInterface interface IG1 { // has default method and + static int _dummy = InitTracker.trackEvent("IG1"); + default int dummy() { return _dummy; } + void doit(); +} + +@FunctionalInterface interface IG2 extends IG1 { // has default method and + static int _dummy = InitTracker.trackEvent("IG2"); + default int dummy() { return _dummy; } +} + +// (IH1/IH2/IH3) +@FunctionalInterface interface IH1 { // has default method and + static int _dummy = InitTracker.trackEvent("IH1"); + default int dummy() { return _dummy; } + void doit(); +} + +@FunctionalInterface interface IH2 extends IH1 { // has but no default method + static int _dummy = InitTracker.trackEvent("IH2"); +} + +@FunctionalInterface interface IH3 extends IH2 { // has but no default method + static int _dummy = InitTracker.trackEvent("IH3"); +} + + +class InitTracker { + static String actualOrder = "InitTracker"; + static int trackEvent(String event) { + actualOrder += ", " + event; + return actualOrder.lastIndexOf(','); + } + static void assertOrder(String wantOrder) { + System.out.println("wantOrder = " + wantOrder); + System.out.println("actualOrder = " + actualOrder); + if (!actualOrder.equals(wantOrder)) { + throw new RuntimeException("Want order: {" + wantOrder + "}, but got {" + actualOrder + "}"); + } + } +} + +interface FooA { + static final int _dummy = InitTracker.trackEvent("FooA"); + default int dummy() { return _dummy; } +} + +interface FooB { + static final int _dummy = InitTracker.trackEvent("FooB"); + default int dummy() { return _dummy; } +} + +class BarA implements FooA { + static {InitTracker.trackEvent("BarA");} + BarA(BarB dummy) {} +} + +class BarB implements FooB { + static {InitTracker.trackEvent("BarB");} +} + +class BarC { + static {InitTracker.trackEvent("BarC");} +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedVarHandles.java b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedVarHandles.java new file mode 100644 index 00000000000..2098ebc2c71 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedVarHandles.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 AOT resolution of VarHandle invocation + * @bug 8343245 + * @requires vm.cds + * @requires vm.cds.supports.aot.class.linking + * @library /test/lib + * @build AOTLinkedVarHandles + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar + * AOTLinkedVarHandlesApp AOTLinkedVarHandlesApp$Data + * @run driver AOTLinkedVarHandles + */ + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import jdk.test.lib.cds.CDSOptions; +import jdk.test.lib.cds.CDSTestUtils; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; + +public class AOTLinkedVarHandles { + static final String classList = "AOTLinkedVarHandles.classlist"; + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + static final String mainClass = AOTLinkedVarHandlesApp.class.getName(); + + public static void main(String[] args) throws Exception { + CDSTestUtils.dumpClassList(classList, "-cp", appJar, mainClass) + .assertNormalExit(output -> { + output.shouldContain("Hello AOTLinkedVarHandlesApp"); + }); + + CDSOptions opts = (new CDSOptions()) + .addPrefix("-XX:ExtraSharedClassListFile=" + classList, + "-XX:+AOTClassLinking", + "-Xlog:cds+resolve=trace", + "-Xlog:cds+class=debug", + "-cp", appJar); + + String s = "archived method CP entry.* AOTLinkedVarHandlesApp "; + OutputAnalyzer dumpOut = CDSTestUtils.createArchiveAndCheck(opts); + dumpOut.shouldMatch(s + "java/lang/invoke/VarHandle.compareAndExchangeAcquire:\\(\\[DIDI\\)D =>"); + dumpOut.shouldMatch(s + "java/lang/invoke/VarHandle.get:\\(\\[DI\\)D => "); + + CDSOptions runOpts = (new CDSOptions()) + .setUseVersion(false) + .addPrefix("-Xlog:cds", + "-esa", + "-cp", appJar) + .addSuffix(mainClass); + + CDSTestUtils.run(runOpts) + .assertNormalExit("Hello AOTLinkedVarHandlesApp"); + } +} + +class AOTLinkedVarHandlesApp { + static final VarHandle initialized; + static final VarHandle lazy; + static long longField = 5678; + static long seed; + + static { + try { + lazy = MethodHandles.lookup().findStaticVarHandle(Data.class, "longField", long.class); + initialized = MethodHandles.lookup().findStaticVarHandle(AOTLinkedVarHandlesApp.class, "longField", long.class); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + static class Data { + static long longField = seed; + } + + public static void main(String args[]) { + seed = 1234; + System.out.println("Hello AOTLinkedVarHandlesApp"); + long a = (long) lazy.get(); + long b = (long) initialized.get(); + System.out.println(a); + System.out.println(b); + if (a != 1234) { + throw new RuntimeException("Data class should not be initialized: " + a); + } + if (b != 5678) { + throw new RuntimeException("VarHandle.get() failed: " + b); + } + + VarHandle vh = MethodHandles.arrayElementVarHandle(double[].class); + double[] array = new double[] {1.0}; + int index = 0; + int v = 4; + + // JDK-8343245 -- this generates "java.lang.invoke.LambdaForm$VH/0x80????" hidden class + double r = (double) vh.compareAndExchangeAcquire(array, index, 1.0, v); + if (r != 1.0) { + throw new RuntimeException("Unexpected result: " + r); + } + r = (double) vh.get(array, index); + if (r != 4.0) { + throw new RuntimeException("Unexpected result: " + r); + } + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/ResolvedConstants.java b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/ResolvedConstants.java index 474fa65d6ea..7d3335b9db6 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/ResolvedConstants.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/ResolvedConstants.java @@ -24,26 +24,40 @@ /* * @test - * @summary Dump time resolutiom of constant pool entries. + * @summary Dump time resolution of constant pool entries. * @requires vm.cds + * @requires vm.cds.supports.aot.class.linking * @requires vm.compMode != "Xcomp" - * @library /test/lib + * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes/ + * @build OldProvider OldClass OldConsumer StringConcatTestOld * @build ResolvedConstants - * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar ResolvedConstantsApp ResolvedConstantsFoo ResolvedConstantsBar + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar + * ResolvedConstantsApp ResolvedConstantsFoo ResolvedConstantsBar + * MyInterface InterfaceWithClinit NormalClass + * OldProvider OldClass OldConsumer SubOfOldClass + * StringConcatTest StringConcatTestOld * @run driver ResolvedConstants */ +import java.util.function.Consumer; import jdk.test.lib.cds.CDSOptions; import jdk.test.lib.cds.CDSTestUtils; import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; public class ResolvedConstants { static final String classList = "ResolvedConstants.classlist"; static final String appJar = ClassFileInstaller.getJarPath("app.jar"); static final String mainClass = ResolvedConstantsApp.class.getName(); + static boolean aotClassLinking; public static void main(String[] args) throws Exception { - // dump class list + test(false); + test(true); + } + + static void test(boolean testMode) throws Exception { + aotClassLinking = testMode; CDSTestUtils.dumpClassList(classList, "-cp", appJar, mainClass) .assertNormalExit(output -> { output.shouldContain("Hello ResolvedConstantsApp"); @@ -52,78 +66,109 @@ public class ResolvedConstants { CDSOptions opts = (new CDSOptions()) .addPrefix("-XX:ExtraSharedClassListFile=" + classList, "-cp", appJar, - "-Xlog:cds+resolve=trace"); - CDSTestUtils.createArchiveAndCheck(opts) + "-Xlog:cds+resolve=trace", + "-Xlog:cds+class=debug"); + if (aotClassLinking) { + opts.addPrefix("-XX:+AOTClassLinking"); + } else { + opts.addPrefix("-XX:-AOTClassLinking"); + } + + OutputAnalyzer out = CDSTestUtils.createArchiveAndCheck(opts); // Class References --- // Always resolve reference when a class references itself - .shouldMatch("cds,resolve.*archived klass.* ResolvedConstantsApp app => ResolvedConstantsApp app") + out.shouldMatch(ALWAYS("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") + .shouldMatch(ALWAYS("klass.* ResolvedConstantsApp app => java/lang/Object boot")) + .shouldMatch(ALWAYS("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") + .shouldMatch(ALWAYS("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") + // Without -XX:+AOTClassLinking: + // java/lang/System is in the boot 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(AOTLINK_ONLY("klass.* ResolvedConstantsApp .*java/lang/System")) // Field References --- // 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") + .shouldMatch(ALWAYS("field.* ResolvedConstantsBar => ResolvedConstantsBar.b:I")) + .shouldMatch(ALWAYS("field.* ResolvedConstantsBar => ResolvedConstantsBar.a:I")) + .shouldMatch(ALWAYS("field.* ResolvedConstantsBar => ResolvedConstantsFoo.a:I")) + .shouldMatch(ALWAYS("field.* ResolvedConstantsFoo => 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") + // Resolve field references to child classes ONLY when using -XX:+AOTClassLinking + .shouldMatch(AOTLINK_ONLY("field.* ResolvedConstantsFoo => ResolvedConstantsBar.a:I")) + .shouldMatch(AOTLINK_ONLY("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") + // Resolve field references to unrelated classes ONLY when using -XX:+AOTClassLinking + .shouldMatch(AOTLINK_ONLY("field.* ResolvedConstantsApp => ResolvedConstantsBar.a:I")) + .shouldMatch(AOTLINK_ONLY("field.* ResolvedConstantsApp => ResolvedConstantsBar.b:I")) // Method References --- // Should resolve references to own constructor - .shouldMatch("cds,resolve.*archived method .* ResolvedConstantsApp ResolvedConstantsApp.:") + .shouldMatch(ALWAYS("method.* ResolvedConstantsApp ResolvedConstantsApp.:")) // Should resolve references to super constructor - .shouldMatch("cds,resolve.*archived method .* ResolvedConstantsApp java/lang/Object.:") + .shouldMatch(ALWAYS("method.* ResolvedConstantsApp java/lang/Object.:")) // Should resolve interface methods in VM classes - .shouldMatch("cds,resolve.*archived interface method .* ResolvedConstantsApp java/lang/Runnable.run:") + .shouldMatch(ALWAYS("interface method .* ResolvedConstantsApp java/lang/Runnable.run:")) // Should resolve references to own non-static method (private or public) - .shouldMatch("archived method.*: ResolvedConstantsBar ResolvedConstantsBar.doBar:") - .shouldMatch("archived method.*: ResolvedConstantsApp ResolvedConstantsApp.privateInstanceCall:") - .shouldMatch("archived method.*: ResolvedConstantsApp ResolvedConstantsApp.publicInstanceCall:") + .shouldMatch(ALWAYS("method.*: ResolvedConstantsBar ResolvedConstantsBar.doBar:")) + .shouldMatch(ALWAYS("method.*: ResolvedConstantsApp ResolvedConstantsApp.privateInstanceCall:")) + .shouldMatch(ALWAYS("method.*: ResolvedConstantsApp ResolvedConstantsApp.publicInstanceCall:")) // Should not resolve references to static method - .shouldNotMatch(" archived method CP entry.*: ResolvedConstantsApp ResolvedConstantsApp.staticCall:") + .shouldNotMatch(ALWAYS("method.*: ResolvedConstantsApp ResolvedConstantsApp.staticCall:")) // Should resolve references to method in super type - .shouldMatch(" archived method CP entry.*: ResolvedConstantsBar ResolvedConstantsFoo.doBar:") + .shouldMatch(ALWAYS("method.*: ResolvedConstantsBar ResolvedConstantsFoo.doBar:")) - // App class cannot resolve references to methods in boot classes: + // Without -XX:+AOTClassLinking App class cannot resolve references to methods in boot classes: // When the app class loader tries to resolve a class X that's normally loaded by // the boot loader, it's possible for the app class loader to get a different copy of // X (by using MethodHandles.Lookup.defineClass(), etc). Therefore, let's be on // the side of safety and revert all such references. - // - // This will be addressed in JDK-8315737. - .shouldMatch("reverted method.*: ResolvedConstantsApp java/io/PrintStream.println:") - .shouldMatch("reverted method.*: ResolvedConstantsBar java/lang/Class.getName:") + .shouldMatch(AOTLINK_ONLY("method.*: ResolvedConstantsApp java/io/PrintStream.println:")) + .shouldMatch(AOTLINK_ONLY("method.*: ResolvedConstantsBar java/lang/Class.getName:")) - // Should not resolve methods in unrelated classes. - .shouldMatch("reverted method.*: ResolvedConstantsApp ResolvedConstantsBar.doit:") + // Resole resolve methods in unrelated classes ONLY when using -XX:+AOTClassLinking + .shouldMatch(AOTLINK_ONLY("method.*: ResolvedConstantsApp ResolvedConstantsBar.doit:")) // End --- ; + + + // Indy References --- + if (aotClassLinking) { + out.shouldContain("Cannot aot-resolve Lambda proxy because OldConsumer is excluded") + .shouldContain("Cannot aot-resolve Lambda proxy because OldProvider is excluded") + .shouldContain("Cannot aot-resolve Lambda proxy because OldClass is excluded") + .shouldContain("Cannot aot-resolve Lambda proxy of interface type InterfaceWithClinit") + .shouldMatch("klasses.* app *NormalClass[$][$]Lambda/.* hidden aot-linked inited") + .shouldNotMatch("klasses.* app *SubOfOldClass[$][$]Lambda/") + .shouldMatch("archived indy *CP entry.*StringConcatTest .* => java/lang/invoke/StringConcatFactory.makeConcatWithConstants") + .shouldNotMatch("archived indy *CP entry.*StringConcatTestOld .* => java/lang/invoke/StringConcatFactory.makeConcatWithConstants"); + } + } + + static String ALWAYS(String s) { + return "cds,resolve.*archived " + s; + } + + static String AOTLINK_ONLY(String s) { + if (aotClassLinking) { + return ALWAYS(s); + } else { + return "cds,resolve.*reverted " + s; + } } } @@ -142,12 +187,98 @@ class ResolvedConstantsApp implements Runnable { bar.a ++; bar.b ++; bar.doit(); + + testLambda(); + StringConcatTest.test(); + StringConcatTestOld.main(null); } private static void staticCall() {} private void privateInstanceCall() {} public void publicInstanceCall() {} public void run() {} + + static void testLambda() { + // The functional type used in the Lambda is an excluded class + OldProvider op = () -> { + return null; + }; + + // A captured value is an instance of an excluded Class + OldClass c = new OldClass(); + Runnable r = () -> { + System.out.println("Test 1 " + c); + }; + r.run(); + + // The functional interface accepts an argument that's an excluded class + MyInterface i = (o) -> { + System.out.println("Test 2 " + o); + }; + i.dispatch(c); + + // Method reference to old class + OldConsumer oldConsumer = new OldConsumer(); + Consumer wrapper = oldConsumer::consumeString; + wrapper.accept("Hello"); + + // Lambda of interfaces that have are not archived. + InterfaceWithClinit i2 = () -> { + System.out.println("Test 3"); + }; + i2.dispatch(); + + // These two classes have almost identical source code, but + // only NormalClass should have its lambdas pre-resolved. + // SubOfOldClass is "old" -- it should be excluded from the AOT cache, + // so none of its lambda proxies should be cached + NormalClass.testLambda(); // Lambda proxy should be cached + SubOfOldClass.testLambda(); // Lambda proxy shouldn't be cached + } +} + +class StringConcatTest { + static void test() { + System.out.println("StringConcatTest " + new StringConcatTest()); // concat should be aot-resolved + } +} + +/* see StringConcatTestOld.jasm + +class StringConcatTestOld { + public static void main(String args[]) { + // concat should be aot-resolved => the MethodType refers to an old class + System.out.println("StringConcatTestOld " + new OldConsumer()); + } +} +*/ + +class NormalClass { + static void testLambda() { + Runnable r = () -> { + System.out.println("NormalClass testLambda"); + }; + r.run(); + } +} + +class SubOfOldClass extends OldClass { + static void testLambda() { + Runnable r = () -> { + System.out.println("SubOfOldClass testLambda"); + }; + r.run(); + } +} + +interface MyInterface { + void dispatch(OldClass c); +} + +interface InterfaceWithClinit { + static final long X = System.currentTimeMillis(); + void dispatch(); + default long dummy() { return X; } } class ResolvedConstantsFoo { diff --git a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/StringConcatTestOld.jasm b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/StringConcatTestOld.jasm new file mode 100644 index 00000000000..76460cad0a3 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/StringConcatTestOld.jasm @@ -0,0 +1,78 @@ +/* + * 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. + * + */ + +/* + +decompiled from + +class OldConsumer {} + +class StringConcatTestOld { + public static void main(String args[]) { + System.out.println("StringConcatTestOld " + new OldConsumer()); + } +} + + +(1) Comment out this line + + invokestatic Method java/lang/String.valueOf:"(Ljava/lang/Object;)Ljava/lang/String;"; + +(2) Change the MethodType parameter of makeConcatWithConstants from + + "(Ljava/lang/String;)Ljava/lang/String;" + -> + + "(LOldConsumer;)Ljava/lang/String;" + +*/ + +super class StringConcatTestOld + version 67:0 +{ + Method "":"()V" + stack 1 locals 1 + { + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + public static Method main:"([Ljava/lang/String;)V" + stack 3 locals 1 + { + getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; + new class OldConsumer; + dup; + invokespecial Method OldConsumer."":"()V"; + //invokestatic Method java/lang/String.valueOf:"(Ljava/lang/Object;)Ljava/lang/String;"; + invokedynamic InvokeDynamic REF_invokeStatic:Method java/lang/invoke/StringConcatFactory.makeConcatWithConstants:"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;":makeConcatWithConstants:"(LOldConsumer;)Ljava/lang/String;" { + String "StringConcatTestOld " + }; + invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; + return; + } + + public static final InnerClass Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles; + +} // end Class StringConcatTestOld diff --git a/test/hotspot/jtreg/runtime/cds/appcds/test-classes/OldConsumer.jasm b/test/hotspot/jtreg/runtime/cds/appcds/test-classes/OldConsumer.jasm new file mode 100644 index 00000000000..bffb5a27381 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/test-classes/OldConsumer.jasm @@ -0,0 +1,53 @@ +/* + * 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. + * + */ +super class OldConsumer + version 49:0 +{ + + +Method "":"()V" + stack 1 locals 1 +{ + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; +} + +public Method consumeString:"(Ljava/lang/String;)V" + stack 3 locals 2 +{ + getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; + new class java/lang/StringBuilder; + dup; + invokespecial Method java/lang/StringBuilder."":"()V"; + ldc String "Hello: "; + invokevirtual Method java/lang/StringBuilder.append:"(Ljava/lang/String;)Ljava/lang/StringBuilder;"; + aload_1; + invokevirtual Method java/lang/StringBuilder.append:"(Ljava/lang/String;)Ljava/lang/StringBuilder;"; + invokevirtual Method java/lang/StringBuilder.toString:"()Ljava/lang/String;"; + invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; + return; +} + +} // end Class OldConsumer diff --git a/test/jdk/ProblemList-AotJdk.txt b/test/jdk/ProblemList-AotJdk.txt new file mode 100644 index 00000000000..9030ba60c78 --- /dev/null +++ b/test/jdk/ProblemList-AotJdk.txt @@ -0,0 +1,11 @@ +java/math/BigInteger/largeMemory/DivisionOverflow.java 0000000 generic-all +java/math/BigInteger/largeMemory/StringConstructorOverflow.java 0000000 generic-all + +jdk/internal/misc/CDS/ArchivedEnumTest.java 0000000 generic-all + +java/lang/module/ModuleDescriptorHashCodeTest.java 0000000 generic-all + + +# The test case is incorrect. There's no guarantee that running a JVM with the following +# parameters will always cause the class java/lang/invoke/MethodHandleStatics to be initialized +java/lang/invoke/DumpMethodHandleInternals.java 0000000 generic-all diff --git a/test/jdk/ProblemList-Xcomp.txt b/test/jdk/ProblemList-Xcomp.txt index 680806e6e31..1577bb6f7f1 100644 --- a/test/jdk/ProblemList-Xcomp.txt +++ b/test/jdk/ProblemList-Xcomp.txt @@ -30,3 +30,4 @@ java/lang/invoke/MethodHandles/CatchExceptionTest.java 8146623 generic-all java/lang/reflect/callerCache/ReflectionCallerCacheTest.java 8332028 generic-all com/sun/jdi/InterruptHangTest.java 8043571 generic-all +jdk/jfr/jvm/TestVirtualThreadExclusion.java 8344199 generic-all diff --git a/test/jdk/java/net/URLPermission/nstest/LookupTest.java b/test/jdk/java/net/URLPermission/nstest/LookupTest.java index 2d6f39a4191..3cbc7a9f124 100644 --- a/test/jdk/java/net/URLPermission/nstest/LookupTest.java +++ b/test/jdk/java/net/URLPermission/nstest/LookupTest.java @@ -201,7 +201,6 @@ public class LookupTest { final PermissionCollection perms = new Permissions(); LookupTestPermisions(int port) { - perms.add(new NetPermission("setProxySelector")); perms.add(new SocketPermission("localhost:1024-", "resolve,accept")); perms.add(new URLPermission("http://allowedAndFound.com:" + port + "/-", "*:*")); perms.add(new URLPermission("http://allowedButNotfound.com:" + port + "/-", "*:*")); diff --git a/test/jdk/java/net/httpclient/PlainProxyConnectionTest.java b/test/jdk/java/net/httpclient/PlainProxyConnectionTest.java index a3157b0ac37..f8671103ec4 100644 --- a/test/jdk/java/net/httpclient/PlainProxyConnectionTest.java +++ b/test/jdk/java/net/httpclient/PlainProxyConnectionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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,8 @@ import com.sun.net.httpserver.HttpContext; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; +import jdk.httpclient.test.lib.common.HttpServerAdapters; + import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; @@ -54,15 +56,28 @@ import static java.net.Proxy.NO_PROXY; * @bug 8230526 * @summary Verifies that PlainProxyConnections are cached and reused properly. We do this by * verifying that the remote address of the HTTP exchange (on the fake proxy server) - * is always the same InetSocketAddress. - * @modules jdk.httpserver - * @run main/othervm -Djdk.tracePinnedThreads=full PlainProxyConnectionTest - * @author danielfuchs + * is always the same InetSocketAddress. Logging verbosity is increased to aid in + * diagnosis of intermittent failures. + * @library /test/lib + * /test/jdk/java/net/httpclient/lib + * @run main/othervm -Djdk.tracePinnedThreads=full + * -Djdk.httpclient.HttpClient.log=headers,requests,trace + * -Djdk.internal.httpclient.debug=true + * PlainProxyConnectionTest */ public class PlainProxyConnectionTest { + // Increase logging verbosity to troubleshoot intermittent failures + static { + HttpServerAdapters.enableServerLogging(); + } + static final String RESPONSE = "

Hello World!"; - static final String PATH = "/foo/"; + + // Adding some salt to the path to avoid other parallel running tests mistakenly connect to our test server + private static final String PATH = String.format( + "/%s-%d", PlainProxyConnectionTest.class.getSimpleName(), PlainProxyConnectionTest.class.hashCode()); + static final ConcurrentLinkedQueue connections = new ConcurrentLinkedQueue<>(); private static final AtomicInteger IDS = new AtomicInteger(); diff --git a/test/jdk/javax/swing/JInternalFrame/bug6726866.java b/test/jdk/javax/swing/JInternalFrame/bug6726866.java index 88b890d7b2c..a8f267fedc2 100644 --- a/test/jdk/javax/swing/JInternalFrame/bug6726866.java +++ b/test/jdk/javax/swing/JInternalFrame/bug6726866.java @@ -23,7 +23,7 @@ /* * @test - * @bug 6726866 8186617 + * @bug 6726866 8186617 8343123 * @summary Repainting artifacts when resizing or dragging JInternalFrames in non-opaque toplevel * @library /java/awt/regtesthelpers @@ -34,7 +34,6 @@ import java.awt.Color; import java.awt.Window; -import javax.swing.JApplet; import javax.swing.JDesktopPane; import javax.swing.JFrame; import javax.swing.JInternalFrame; diff --git a/test/jdk/jdk/internal/misc/CDS/ArchivedEnumTest.java b/test/jdk/jdk/internal/misc/CDS/ArchivedEnumTest.java index ec1bd213834..3a2d0f9539f 100644 --- a/test/jdk/jdk/internal/misc/CDS/ArchivedEnumTest.java +++ b/test/jdk/jdk/internal/misc/CDS/ArchivedEnumTest.java @@ -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 @@ -35,6 +35,7 @@ * @run driver ArchivedEnumTest */ +import jdk.test.lib.cds.CDSTestUtils; import jdk.test.lib.helpers.ClassFileInstaller; import jdk.test.lib.process.OutputAnalyzer; @@ -46,7 +47,7 @@ public class ArchivedEnumTest { TestCommon.list("ArchivedEnumApp")); // Note: You can get the following line to fail by commenting out // the ADD_EXCL(...) lines in cdsHeapVerifier.cpp - out.shouldNotContain("object points to a static field that may be reinitialized at runtime"); + out.shouldNotContain(CDSTestUtils.MSG_STATIC_FIELD_MAY_HOLD_DIFFERENT_VALUE); TestCommon.run("-cp", appJar, "-Xlog:cds=debug", diff --git a/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/AnnotationsTest.java b/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/AnnotationsTest.java new file mode 100644 index 00000000000..2d23f49cdd7 --- /dev/null +++ b/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/AnnotationsTest.java @@ -0,0 +1,408 @@ +/* + * 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. + */ +package jdk.jpackage.test; + +import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.time.LocalDate; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import static java.util.stream.Collectors.toMap; +import java.util.stream.Stream; +import jdk.internal.util.OperatingSystem; +import static jdk.internal.util.OperatingSystem.LINUX; +import jdk.jpackage.test.Annotations.Parameter; +import jdk.jpackage.test.Annotations.ParameterSupplier; +import jdk.jpackage.test.Annotations.Parameters; +import jdk.jpackage.test.Annotations.Test; +import static jdk.jpackage.test.Functional.ThrowingSupplier.toSupplier; + +/* + * @test + * @summary Test jpackage test library's annotation processor + * @library /test/jdk/tools/jpackage/helpers + * @build jdk.jpackage.test.* + * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.AnnotationsTest + */ +public class AnnotationsTest { + + public static void main(String... args) { + runTests(BasicTest.class, ParameterizedInstanceTest.class); + for (var os : OperatingSystem.values()) { + try { + TestBuilderConfig.setOperatingSystem(os); + TKit.log("Current operating system: " + os); + runTests(IfOSTest.class); + } finally { + TestBuilderConfig.setDefaults(); + } + } + } + + public static class BasicTest extends TestExecutionRecorder { + @Test + public void testNoArg() { + recordTestCase(); + } + + @Test + @Parameter("TRUE") + public int testNoArg(boolean v) { + recordTestCase(v); + return 0; + } + + @Test + @Parameter({}) + @Parameter("a") + @Parameter({"b", "c"}) + public void testVarArg(Path ... paths) { + recordTestCase((Object[]) paths); + } + + @Test + @Parameter({"12", "foo"}) + @Parameter({"-89", "bar", "more"}) + @Parameter({"-89", "bar", "more", "moore"}) + public void testVarArg2(int a, String b, String ... other) { + recordTestCase(a, b, other); + } + + @Test + @ParameterSupplier("dateSupplier") + @ParameterSupplier("jdk.jpackage.test.AnnotationsTest.dateSupplier") + public void testDates(LocalDate v) { + recordTestCase(v); + } + + public static Set getExpectedTestDescs() { + return Set.of( + "().testNoArg()", + "().testNoArg(true)", + "().testVarArg()", + "().testVarArg(a)", + "().testVarArg(b, c)", + "().testVarArg2(-89, bar, [more, moore](length=2))", + "().testVarArg2(-89, bar, [more](length=1))", + "().testVarArg2(12, foo, [](length=0))", + "().testDates(2018-05-05)", + "().testDates(2018-07-11)", + "().testDates(2034-05-05)", + "().testDates(2056-07-11)" + ); + } + + public static Collection dateSupplier() { + return List.of(new Object[][] { + { LocalDate.parse("2018-05-05") }, + { LocalDate.parse("2018-07-11") }, + }); + } + } + + public static class ParameterizedInstanceTest extends TestExecutionRecorder { + public ParameterizedInstanceTest(String... args) { + super((Object[]) args); + } + + public ParameterizedInstanceTest(int o) { + super(o); + } + + public ParameterizedInstanceTest(int a, Boolean[] b, String c, String ... other) { + super(a, b, c, other); + } + + @Test + public void testNoArgs() { + recordTestCase(); + } + + @Test + @ParameterSupplier("jdk.jpackage.test.AnnotationsTest.dateSupplier") + public void testDates(LocalDate v) { + recordTestCase(v); + } + + @Test + @Parameter("a") + public static void staticTest(String arg) { + staticRecorder.recordTestCase(arg); + } + + @Parameters + public static Collection input() { + return List.of(new Object[][] { + {}, + {55, new Boolean[]{false, true, false}, "foo", "bar"}, + {78}, + }); + } + + @Parameters + public static Collection input2() { + return List.of(new Object[][] { + {51, new boolean[]{true, true, true}, "foo"}, + {33}, + {55, null, null }, + {55, null, null, "1" }, + }); + } + + public static Set getExpectedTestDescs() { + return Set.of( + "().testNoArgs()", + "(33).testNoArgs()", + "(78).testNoArgs()", + "(55, [false, true, false](length=3), foo, [bar](length=1)).testNoArgs()", + "(51, [true, true, true](length=3), foo, [](length=0)).testNoArgs()", + "().testDates(2034-05-05)", + "().testDates(2056-07-11)", + "(33).testDates(2034-05-05)", + "(33).testDates(2056-07-11)", + "(51, [true, true, true](length=3), foo, [](length=0)).testDates(2034-05-05)", + "(51, [true, true, true](length=3), foo, [](length=0)).testDates(2056-07-11)", + "(55, [false, true, false](length=3), foo, [bar](length=1)).testDates(2034-05-05)", + "(55, [false, true, false](length=3), foo, [bar](length=1)).testDates(2056-07-11)", + "(78).testDates(2034-05-05)", + "(78).testDates(2056-07-11)", + "(55, null, null, [1](length=1)).testDates(2034-05-05)", + "(55, null, null, [1](length=1)).testDates(2056-07-11)", + "(55, null, null, [1](length=1)).testNoArgs()", + "(55, null, null, [](length=0)).testDates(2034-05-05)", + "(55, null, null, [](length=0)).testDates(2056-07-11)", + "(55, null, null, [](length=0)).testNoArgs()", + "().staticTest(a)" + ); + } + + private final static TestExecutionRecorder staticRecorder = new TestExecutionRecorder(ParameterizedInstanceTest.class); + } + + public static class IfOSTest extends TestExecutionRecorder { + public IfOSTest(int a, String b) { + super(a, b); + } + + @Test(ifOS = OperatingSystem.LINUX) + public void testNoArgs() { + recordTestCase(); + } + + @Test(ifNotOS = OperatingSystem.LINUX) + public void testNoArgs2() { + recordTestCase(); + } + + @Test + @Parameter(value = "foo", ifOS = OperatingSystem.LINUX) + @Parameter(value = {"foo", "bar"}, ifOS = { OperatingSystem.LINUX, OperatingSystem.MACOS }) + @Parameter(value = {}, ifNotOS = { OperatingSystem.WINDOWS }) + public void testVarArgs(String ... args) { + recordTestCase((Object[]) args); + } + + @Test + @ParameterSupplier(value = "jdk.jpackage.test.AnnotationsTest.dateSupplier", ifOS = OperatingSystem.WINDOWS) + public void testDates(LocalDate v) { + recordTestCase(v); + } + + @Parameters(ifOS = OperatingSystem.LINUX) + public static Collection input() { + return Set.of(new Object[][] { + {7, null}, + }); + } + + @Parameters(ifNotOS = {OperatingSystem.LINUX, OperatingSystem.MACOS}) + public static Collection input2() { + return Set.of(new Object[][] { + {10, "hello"}, + }); + } + + @Parameters(ifNotOS = OperatingSystem.LINUX) + public static Collection input3() { + return Set.of(new Object[][] { + {15, "bye"}, + }); + } + + public static Set getExpectedTestDescs() { + switch (TestBuilderConfig.getDefault().getOperatingSystem()) { + case LINUX -> { + return Set.of( + "(7, null).testNoArgs()", + "(7, null).testVarArgs()", + "(7, null).testVarArgs(foo)", + "(7, null).testVarArgs(foo, bar)" + ); + } + + case MACOS -> { + return Set.of( + "(15, bye).testNoArgs2()", + "(15, bye).testVarArgs()", + "(15, bye).testVarArgs(foo, bar)" + ); + } + + case WINDOWS -> { + return Set.of( + "(15, bye).testDates(2034-05-05)", + "(15, bye).testDates(2056-07-11)", + "(15, bye).testNoArgs2()", + "(10, hello).testDates(2034-05-05)", + "(10, hello).testDates(2056-07-11)", + "(10, hello).testNoArgs2()" + ); + } + + case AIX -> { + return Set.of( + ); + } + } + + throw new UnsupportedOperationException(); + } + } + + public static Collection dateSupplier() { + return List.of(new Object[][] { + { LocalDate.parse("2034-05-05") }, + { LocalDate.parse("2056-07-11") }, + }); + } + + private static void runTests(Class... tests) { + ACTUAL_TEST_DESCS.get().clear(); + + var expectedTestDescs = Stream.of(tests) + .map(AnnotationsTest::getExpectedTestDescs) + .flatMap(x -> x) + // Collect in the map to check for collisions for free + .collect(toMap(x -> x, x -> "")) + .keySet(); + + var args = Stream.of(tests).map(test -> { + return String.format("--jpt-run=%s", test.getName()); + }).toArray(String[]::new); + + try { + Main.main(args); + assertRecordedTestDescs(expectedTestDescs); + } catch (Throwable t) { + t.printStackTrace(System.err); + System.exit(1); + } + } + + private static Stream getExpectedTestDescs(Class type) { + return toSupplier(() -> { + var method = type.getMethod("getExpectedTestDescs"); + var testDescPefix = type.getName(); + return ((Set)method.invoke(null)).stream().map(desc -> { + return testDescPefix + desc; + }); + }).get(); + } + + private static void assertRecordedTestDescs(Set expectedTestDescs) { + var comm = Comm.compare(expectedTestDescs, ACTUAL_TEST_DESCS.get()); + if (!comm.unique1().isEmpty()) { + System.err.println("Missing test case signatures:"); + comm.unique1().stream().sorted().sequential().forEachOrdered(System.err::println); + System.err.println("<>"); + } + + if (!comm.unique2().isEmpty()) { + System.err.println("Unexpected test case signatures:"); + comm.unique2().stream().sorted().sequential().forEachOrdered(System.err::println); + System.err.println("<>"); + } + + if (!comm.unique2().isEmpty() || !comm.unique1().isEmpty()) { + // Don't use TKit asserts as this call is outside the test execution + throw new AssertionError("Test case signatures mismatched"); + } + } + + private static class TestExecutionRecorder { + protected TestExecutionRecorder(Object ... args) { + this.testClass = getClass(); + this.testDescBuilder = TestInstance.TestDesc.createBuilder().ctorArgs(args); + } + + TestExecutionRecorder(Class testClass) { + this.testClass = testClass; + this.testDescBuilder = TestInstance.TestDesc.createBuilder().ctorArgs(); + } + + protected void recordTestCase(Object ... args) { + testDescBuilder.methodArgs(args).method(getCurrentTestCase()); + var testCaseDescs = ACTUAL_TEST_DESCS.get(); + var testCaseDesc = testDescBuilder.get().testFullName(); + TKit.assertTrue(!testCaseDescs.contains(testCaseDesc), String.format( + "Check this test case is executed for the first time", + testCaseDesc)); + TKit.assertTrue(!executed, "Check this test case instance is not reused"); + executed = true; + testCaseDescs.add(testCaseDesc); + } + + private Method getCurrentTestCase() { + return StackWalker.getInstance(RETAIN_CLASS_REFERENCE).walk(frames -> { + return frames.map(frame -> { + var methodType = frame.getMethodType(); + var methodName = frame.getMethodName(); + var methodReturn = methodType.returnType(); + var methodParameters = methodType.parameterArray(); + return Stream.of(testClass.getDeclaredMethods()).filter(method -> { + return method.getName().equals(methodName) + && method.getReturnType().equals(methodReturn) + && Arrays.equals(method.getParameterTypes(), methodParameters) + && method.isAnnotationPresent(Test.class); + }).findFirst(); + }).dropWhile(Optional::isEmpty).map(Optional::get).findFirst(); + }).get(); + } + + private boolean executed; + private final TestInstance.TestDesc.Builder testDescBuilder; + private final Class testClass; + } + + private static final ThreadLocal> ACTUAL_TEST_DESCS = new ThreadLocal<>() { + @Override + protected Set initialValue() { + return new HashSet<>(); + } + }; +} diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Annotations.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Annotations.java index d29133ee3e1..ad1e77b4171 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Annotations.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Annotations.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -27,6 +27,7 @@ import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import jdk.internal.util.OperatingSystem; public class Annotations { @@ -43,6 +44,14 @@ public class Annotations { @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Test { + + OperatingSystem[] ifOS() default { + OperatingSystem.LINUX, + OperatingSystem.WINDOWS, + OperatingSystem.MACOS + }; + + OperatingSystem[] ifNotOS() default {}; } @Retention(RetentionPolicy.RUNTIME) @@ -51,6 +60,14 @@ public class Annotations { public @interface Parameter { String[] value(); + + OperatingSystem[] ifOS() default { + OperatingSystem.LINUX, + OperatingSystem.WINDOWS, + OperatingSystem.MACOS + }; + + OperatingSystem[] ifNotOS() default {}; } @Retention(RetentionPolicy.RUNTIME) @@ -60,8 +77,39 @@ public class Annotations { Parameter[] value(); } + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + @Repeatable(ParameterSupplierGroup.class) + public @interface ParameterSupplier { + + String value(); + + OperatingSystem[] ifOS() default { + OperatingSystem.LINUX, + OperatingSystem.WINDOWS, + OperatingSystem.MACOS + }; + + OperatingSystem[] ifNotOS() default {}; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface ParameterSupplierGroup { + + ParameterSupplier[] value(); + } + @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Parameters { + + OperatingSystem[] ifOS() default { + OperatingSystem.LINUX, + OperatingSystem.WINDOWS, + OperatingSystem.MACOS + }; + + OperatingSystem[] ifNotOS() default {}; } } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Comm.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Comm.java new file mode 100644 index 00000000000..23798f326fe --- /dev/null +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Comm.java @@ -0,0 +1,39 @@ +/* + * 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. + */ +package jdk.jpackage.test; + +import java.util.HashSet; +import java.util.Set; + +record Comm(Set common, Set unique1, Set unique2) { + + static Comm compare(Set a, Set b) { + Set common = new HashSet<>(a); + common.retainAll(b); + Set unique1 = new HashSet<>(a); + unique1.removeAll(common); + Set unique2 = new HashSet<>(b); + unique2.removeAll(common); + return new Comm(common, unique1, unique2); + } +} diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java index bf5ced09bc7..a96bab49355 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -40,6 +40,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; +import jdk.jpackage.internal.ApplicationLayout; import jdk.jpackage.internal.IOUtils; import jdk.jpackage.test.Functional.ThrowingConsumer; import jdk.jpackage.test.PackageTest.PackageHandlers; @@ -399,6 +400,15 @@ public final class LinuxHelper { private static void verifyDesktopFile(JPackageCommand cmd, Path desktopFile) throws IOException { TKit.trace(String.format("Check [%s] file BEGIN", desktopFile)); + + var launcherName = Stream.of(List.of(cmd.name()), cmd.addLauncherNames()).flatMap(List::stream).filter(name -> { + return getDesktopFile(cmd, name).equals(desktopFile); + }).findAny(); + if (!cmd.hasArgument("--app-image")) { + TKit.assertTrue(launcherName.isPresent(), + "Check the desktop file corresponds to one of app launchers"); + } + List lines = Files.readAllLines(desktopFile); TKit.assertEquals("[Desktop Entry]", lines.get(0), "Check file header"); @@ -428,7 +438,7 @@ public final class LinuxHelper { "Check value of [%s] key", key)); } - // Verify value of `Exec` property in .desktop files are escaped if required + // Verify the value of `Exec` key is escaped if required String launcherPath = data.get("Exec"); if (Pattern.compile("\\s").matcher(launcherPath).find()) { TKit.assertTrue(launcherPath.startsWith("\"") @@ -437,10 +447,25 @@ public final class LinuxHelper { launcherPath = launcherPath.substring(1, launcherPath.length() - 1); } - Stream.of(launcherPath, data.get("Icon")) - .map(Path::of) - .map(cmd::pathToUnpackedPackageFile) - .forEach(TKit::assertFileExists); + if (launcherName.isPresent()) { + TKit.assertEquals(launcherPath, cmd.pathToPackageFile( + cmd.appLauncherPath(launcherName.get())).toString(), + String.format( + "Check the value of [Exec] key references [%s] app launcher", + launcherName.get())); + } + + for (var e : List.>, Function>>of( + Map.entry(Map.entry("Exec", Optional.of(launcherPath)), ApplicationLayout::launchersDirectory), + Map.entry(Map.entry("Icon", Optional.empty()), ApplicationLayout::destktopIntegrationDirectory))) { + var path = e.getKey().getValue().or(() -> Optional.of(data.get( + e.getKey().getKey()))).map(Path::of).get(); + TKit.assertFileExists(cmd.pathToUnpackedPackageFile(path)); + Path expectedDir = cmd.pathToPackageFile(e.getValue().apply(cmd.appLayout())); + TKit.assertTrue(path.getParent().equals(expectedDir), String.format( + "Check the value of [%s] key references a file in [%s] folder", + e.getKey().getKey(), expectedDir)); + } TKit.trace(String.format("Check [%s] file END", desktopFile)); } @@ -725,10 +750,10 @@ public final class LinuxHelper { private static Map archs; - private final static Pattern XDG_CMD_ICON_SIZE_PATTERN = Pattern.compile("\\s--size\\s+(\\d+)\\b"); + private static final Pattern XDG_CMD_ICON_SIZE_PATTERN = Pattern.compile("\\s--size\\s+(\\d+)\\b"); // Values grabbed from https://linux.die.net/man/1/xdg-icon-resource - private final static Set XDG_CMD_VALID_ICON_SIZES = Set.of(16, 22, 32, 48, 64, 128); + private static final Set XDG_CMD_VALID_ICON_SIZES = Set.of(16, 22, 32, 48, 64, 128); - private final static Method getServiceUnitFileName = initGetServiceUnitFileName(); + private static final Method getServiceUnitFileName = initGetServiceUnitFileName(); } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Main.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Main.java index 37aca48ac17..5919d8361c4 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Main.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Main.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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,11 +23,17 @@ package jdk.jpackage.test; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Comparator; +import java.util.Deque; import java.util.List; import java.util.function.Function; import java.util.function.Predicate; -import java.util.stream.Collectors; +import static java.util.stream.Collectors.toCollection; +import java.util.stream.Stream; import static jdk.jpackage.test.TestBuilder.CMDLINE_ARG_PREFIX; @@ -36,7 +42,9 @@ public final class Main { boolean listTests = false; List tests = new ArrayList<>(); try (TestBuilder testBuilder = new TestBuilder(tests::add)) { - for (var arg : args) { + Deque argsAsList = new ArrayDeque<>(List.of(args)); + while (!argsAsList.isEmpty()) { + var arg = argsAsList.pop(); TestBuilder.trace(String.format("Parsing [%s]...", arg)); if ((CMDLINE_ARG_PREFIX + "list").equals(arg)) { @@ -44,6 +52,29 @@ public final class Main { continue; } + if (arg.startsWith("@")) { + // Command file + // @=args will read arguments from the "args" file, one argument per line + // @args will read arguments from the "args" file, splitting lines into arguments at whitespaces + arg = arg.substring(1); + var oneArgPerLine = arg.startsWith("="); + if (oneArgPerLine) { + arg = arg.substring(1); + } + + var newArgsStream = Files.readAllLines(Path.of(arg)).stream(); + if (!oneArgPerLine) { + newArgsStream.map(line -> { + return Stream.of(line.split("\\s+")); + }).flatMap(x -> x); + } + + var newArgs = newArgsStream.collect(toCollection(ArrayDeque::new)); + newArgs.addAll(argsAsList); + argsAsList = newArgs; + continue; + } + boolean success = false; try { testBuilder.processCmdLineArg(arg); @@ -62,12 +93,11 @@ public final class Main { // Order tests by their full names to have stable test sequence. List orderedTests = tests.stream() - .sorted((a, b) -> a.fullName().compareTo(b.fullName())) - .collect(Collectors.toList()); + .sorted(Comparator.comparing(TestInstance::fullName)).toList(); if (listTests) { // Just list the tests - orderedTests.stream().forEach(test -> System.out.println(String.format( + orderedTests.forEach(test -> System.out.println(String.format( "%s; workDir=[%s]", test.fullName(), test.workDir()))); return; } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MethodCall.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MethodCall.java index 2aaba054a9b..d5b8bd702c8 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MethodCall.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MethodCall.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -22,34 +22,33 @@ */ package jdk.jpackage.test; +import java.lang.reflect.Array; import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; -import java.util.List; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.function.Supplier; -import java.util.stream.Collectors; +import java.util.function.Predicate; +import java.util.stream.IntStream; import java.util.stream.Stream; import jdk.jpackage.test.Functional.ThrowingConsumer; import jdk.jpackage.test.TestInstance.TestDesc; class MethodCall implements ThrowingConsumer { - MethodCall(Object[] instanceCtorArgs, Method method) { - this.ctorArgs = Optional.ofNullable(instanceCtorArgs).orElse( - DEFAULT_CTOR_ARGS); - this.method = method; - this.methodArgs = new Object[0]; - } + MethodCall(Object[] instanceCtorArgs, Method method, Object ... args) { + Objects.requireNonNull(instanceCtorArgs); + Objects.requireNonNull(method); - MethodCall(Object[] instanceCtorArgs, Method method, Object arg) { - this.ctorArgs = Optional.ofNullable(instanceCtorArgs).orElse( - DEFAULT_CTOR_ARGS); + this.ctorArgs = instanceCtorArgs; this.method = method; - this.methodArgs = new Object[]{arg}; + this.methodArgs = args; } TestDesc createDescription() { @@ -76,68 +75,35 @@ class MethodCall implements ThrowingConsumer { return null; } - Constructor ctor = findRequiredConstructor(method.getDeclaringClass(), - ctorArgs); - if (ctor.isVarArgs()) { - // Assume constructor doesn't have fixed, only variable parameters. - return ctor.newInstance(new Object[]{ctorArgs}); - } + var ctor = findMatchingConstructor(method.getDeclaringClass(), ctorArgs); - return ctor.newInstance(ctorArgs); + return ctor.newInstance(mapArgs(ctor, ctorArgs)); + } + + static Object[] mapArgs(Executable executable, final Object ... args) { + return mapPrimitiveTypeArgs(executable, mapVarArgs(executable, args)); } void checkRequiredConstructor() throws NoSuchMethodException { if ((method.getModifiers() & Modifier.STATIC) == 0) { - findRequiredConstructor(method.getDeclaringClass(), ctorArgs); + findMatchingConstructor(method.getDeclaringClass(), ctorArgs); } } - private static Constructor findVarArgConstructor(Class type) { - return Stream.of(type.getConstructors()).filter( - Constructor::isVarArgs).findFirst().orElse(null); - } - - private Constructor findRequiredConstructor(Class type, Object... ctorArgs) + private static Constructor findMatchingConstructor(Class type, Object... ctorArgs) throws NoSuchMethodException { - Supplier notFoundException = () -> { - return new NoSuchMethodException(String.format( + var ctors = filterMatchingExecutablesForParameterValues(Stream.of( + type.getConstructors()), ctorArgs).toList(); + + if (ctors.size() != 1) { + // No public constructors that can handle the given arguments. + throw new NoSuchMethodException(String.format( "No public contructor in %s for %s arguments", type, Arrays.deepToString(ctorArgs))); - }; - - if (Stream.of(ctorArgs).allMatch(Objects::nonNull)) { - // No `null` in constructor args, take easy path - try { - return type.getConstructor(Stream.of(ctorArgs).map( - Object::getClass).collect(Collectors.toList()).toArray( - Class[]::new)); - } catch (NoSuchMethodException ex) { - // Failed to find ctor that can take the given arguments. - Constructor varArgCtor = findVarArgConstructor(type); - if (varArgCtor != null) { - // There is one with variable number of arguments. Use it. - return varArgCtor; - } - throw notFoundException.get(); - } } - List ctors = Stream.of(type.getConstructors()) - .filter(ctor -> ctor.getParameterCount() == ctorArgs.length) - .collect(Collectors.toList()); - - if (ctors.isEmpty()) { - // No public constructors that can handle the given arguments. - throw notFoundException.get(); - } - - if (ctors.size() == 1) { - return ctors.iterator().next(); - } - - // Revisit this tricky case when it will start bothering. - throw notFoundException.get(); + return ctors.get(0); } @Override @@ -145,9 +111,159 @@ class MethodCall implements ThrowingConsumer { method.invoke(thiz, methodArgs); } + private static Object[] mapVarArgs(Executable executable, final Object ... args) { + if (executable.isVarArgs()) { + var paramTypes = executable.getParameterTypes(); + Class varArgParamType = paramTypes[paramTypes.length - 1]; + + Object[] newArgs; + if (paramTypes.length - args.length == 1) { + // Empty var args + + // "args" can be of type String[] if the "executable" is "foo(String ... str)" + newArgs = Arrays.copyOf(args, args.length + 1, Object[].class); + newArgs[newArgs.length - 1] = Array.newInstance(varArgParamType.componentType(), 0); + } else { + var varArgs = Arrays.copyOfRange(args, paramTypes.length - 1, + args.length, varArgParamType); + + // "args" can be of type String[] if the "executable" is "foo(String ... str)" + newArgs = Arrays.copyOfRange(args, 0, paramTypes.length, Object[].class); + newArgs[newArgs.length - 1] = varArgs; + } + return newArgs; + } + + return args; + } + + private static Object[] mapPrimitiveTypeArgs(Executable executable, final Object ... args) { + var paramTypes = executable.getParameterTypes(); + if (paramTypes.length != args.length) { + throw new IllegalArgumentException( + "The number of arguments must be equal to the number of parameters of the executable"); + } + + if (IntStream.range(0, args.length).allMatch(idx -> { + return Optional.ofNullable(args[idx]).map(Object::getClass).map(paramTypes[idx]::isAssignableFrom).orElse(true); + })) { + return args; + } else { + final var newArgs = Arrays.copyOf(args, args.length, Object[].class); + for (var idx = 0; idx != args.length; ++idx) { + final var paramType = paramTypes[idx]; + final var argValue = args[idx]; + newArgs[idx] = Optional.ofNullable(argValue).map(Object::getClass).map(argType -> { + if(argType.isArray() && !paramType.isAssignableFrom(argType)) { + var length = Array.getLength(argValue); + var newArray = Array.newInstance(paramType.getComponentType(), length); + for (var arrayIdx = 0; arrayIdx != length; ++arrayIdx) { + Array.set(newArray, arrayIdx, Array.get(argValue, arrayIdx)); + } + return newArray; + } else { + return argValue; + } + }).orElse(argValue); + } + + return newArgs; + } + } + + private static Stream filterMatchingExecutablesForParameterValues( + Stream executables, Object... args) { + return filterMatchingExecutablesForParameterTypes( + executables, + Stream.of(args) + .map(arg -> arg != null ? arg.getClass() : null) + .toArray(Class[]::new)); + } + + private static Stream filterMatchingExecutablesForParameterTypes( + Stream executables, Class... argTypes) { + return executables.filter(executable -> { + var parameterTypes = executable.getParameterTypes(); + + final int checkArgTypeCount; + if (parameterTypes.length <= argTypes.length) { + checkArgTypeCount = parameterTypes.length; + } else if (parameterTypes.length - argTypes.length == 1 && executable.isVarArgs()) { + // Empty optional arguments. + checkArgTypeCount = argTypes.length; + } else { + // Not enough mandatory arguments. + return false; + } + + var unmatched = IntStream.range(0, checkArgTypeCount).dropWhile(idx -> { + return new ParameterTypeMatcher(parameterTypes[idx]).test(argTypes[idx]); + }).toArray(); + + if (argTypes.length == parameterTypes.length && unmatched.length == 0) { + // Number of argument types equals to the number of parameters + // of the executable and all types match. + return true; + } + + if (executable.isVarArgs()) { + var varArgType = parameterTypes[parameterTypes.length - 1].componentType(); + return IntStream.of(unmatched).allMatch(idx -> { + return new ParameterTypeMatcher(varArgType).test(argTypes[idx]); + }); + } + + return false; + }); + } + + private static final class ParameterTypeMatcher implements Predicate> { + ParameterTypeMatcher(Class parameterType) { + Objects.requireNonNull(parameterType); + this.parameterType = NORM_TYPES.getOrDefault(parameterType, parameterType); + } + + @Override + public boolean test(Class paramaterValueType) { + if (paramaterValueType == null) { + return true; + } + + paramaterValueType = NORM_TYPES.getOrDefault(paramaterValueType, paramaterValueType); + return parameterType.isAssignableFrom(paramaterValueType); + } + + private final Class parameterType; + } + private final Object[] methodArgs; private final Method method; private final Object[] ctorArgs; - final static Object[] DEFAULT_CTOR_ARGS = new Object[0]; + private static final Map, Class> NORM_TYPES; + + static { + Map, Class> primitives = Map.of( + boolean.class, Boolean.class, + byte.class, Byte.class, + short.class, Short.class, + int.class, Integer.class, + long.class, Long.class, + float.class, Float.class, + double.class, Double.class); + + Map, Class> primitiveArrays = Map.of( + boolean[].class, Boolean[].class, + byte[].class, Byte[].class, + short[].class, Short[].class, + int[].class, Integer[].class, + long[].class, Long[].class, + float[].class, Float[].class, + double[].class, Double[].class); + + Map, Class> combined = new HashMap<>(primitives); + combined.putAll(primitiveArrays); + + NORM_TYPES = Collections.unmodifiableMap(combined); + } } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java index 28c58a4db3d..957449697a5 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java @@ -781,18 +781,18 @@ public final class TKit { currentTest.notifyAssert(); var comm = Comm.compare(content, expected); - if (!comm.unique1.isEmpty() && !comm.unique2.isEmpty()) { + if (!comm.unique1().isEmpty() && !comm.unique2().isEmpty()) { error(String.format( "assertDirectoryContentEquals(%s): Some expected %s. Unexpected %s. Missing %s", - baseDir, format(comm.common), format(comm.unique1), format(comm.unique2))); - } else if (!comm.unique1.isEmpty()) { + baseDir, format(comm.common()), format(comm.unique1()), format(comm.unique2()))); + } else if (!comm.unique1().isEmpty()) { error(String.format( "assertDirectoryContentEquals(%s): Expected %s. Unexpected %s", - baseDir, format(comm.common), format(comm.unique1))); - } else if (!comm.unique2.isEmpty()) { + baseDir, format(comm.common()), format(comm.unique1()))); + } else if (!comm.unique2().isEmpty()) { error(String.format( "assertDirectoryContentEquals(%s): Some expected %s. Missing %s", - baseDir, format(comm.common), format(comm.unique2))); + baseDir, format(comm.common()), format(comm.unique2()))); } else { traceAssert(String.format( "assertDirectoryContentEquals(%s): Expected %s", @@ -808,10 +808,10 @@ public final class TKit { currentTest.notifyAssert(); var comm = Comm.compare(content, expected); - if (!comm.unique2.isEmpty()) { + if (!comm.unique2().isEmpty()) { error(String.format( "assertDirectoryContentContains(%s): Some expected %s. Missing %s", - baseDir, format(comm.common), format(comm.unique2))); + baseDir, format(comm.common()), format(comm.unique2()))); } else { traceAssert(String.format( "assertDirectoryContentContains(%s): Expected %s", @@ -838,21 +838,6 @@ public final class TKit { this.content = contents; } - private static record Comm(Set common, Set unique1, Set unique2) { - static Comm compare(Set a, Set b) { - Set common = new HashSet<>(a); - common.retainAll(b); - - Set unique1 = new HashSet<>(a); - unique1.removeAll(common); - - Set unique2 = new HashSet<>(b); - unique2.removeAll(common); - - return new Comm(common, unique1, unique2); - } - } - private static String format(Set paths) { return Arrays.toString( paths.stream().sorted().map(Path::toString).toArray( diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestBuilder.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestBuilder.java index bb699ba3b9c..c2fc1789a25 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestBuilder.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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,32 +23,28 @@ package jdk.jpackage.test; -import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.util.ArrayList; -import java.util.Collection; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.UnaryOperator; import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.jpackage.test.Annotations.AfterEach; import jdk.jpackage.test.Annotations.BeforeEach; -import jdk.jpackage.test.Annotations.Parameter; -import jdk.jpackage.test.Annotations.ParameterGroup; -import jdk.jpackage.test.Annotations.Parameters; import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.Functional.ThrowingConsumer; +import static jdk.jpackage.test.Functional.ThrowingConsumer.toConsumer; import jdk.jpackage.test.Functional.ThrowingFunction; +import jdk.jpackage.test.TestMethodSupplier.InvalidAnnotationException; +import static jdk.jpackage.test.TestMethodSupplier.MethodQuery.fromQualifiedMethodName; final class TestBuilder implements AutoCloseable { @@ -58,6 +54,7 @@ final class TestBuilder implements AutoCloseable { } TestBuilder(Consumer testConsumer) { + this.testMethodSupplier = TestBuilderConfig.getDefault().createTestMethodSupplier(); argProcessors = Map.of( CMDLINE_ARG_PREFIX + "after-run", arg -> getJavaMethodsFromArg(arg).map( @@ -70,7 +67,7 @@ final class TestBuilder implements AutoCloseable { CMDLINE_ARG_PREFIX + "run", arg -> addTestGroup(getJavaMethodsFromArg(arg).map( ThrowingFunction.toFunction( - TestBuilder::toMethodCalls)).flatMap(s -> s).collect( + this::toMethodCalls)).flatMap(s -> s).collect( Collectors.toList())), CMDLINE_ARG_PREFIX + "exclude", @@ -219,23 +216,29 @@ final class TestBuilder implements AutoCloseable { .filter(m -> m.getParameterCount() == 0) .filter(m -> !m.isAnnotationPresent(Test.class)) .filter(m -> m.isAnnotationPresent(annotationType)) - .sorted((a, b) -> a.getName().compareTo(b.getName())); + .sorted(Comparator.comparing(Method::getName)); } - private static Stream cmdLineArgValueToMethodNames(String v) { + private Stream cmdLineArgValueToMethodNames(String v) { List result = new ArrayList<>(); String defaultClassName = null; for (String token : v.split(",")) { Class testSet = probeClass(token); if (testSet != null) { + if (testMethodSupplier.isTestClass(testSet)) { + toConsumer(testMethodSupplier::verifyTestClass).accept(testSet); + } + // Test set class specified. Pull in all public methods // from the class with @Test annotation removing name duplicates. // Overloads will be handled at the next phase of processing. defaultClassName = token; - Stream.of(testSet.getMethods()).filter( - m -> m.isAnnotationPresent(Test.class)).map( - Method::getName).distinct().forEach( - name -> result.add(String.join(".", token, name))); + result.addAll(Stream.of(testSet.getMethods()) + .filter(m -> m.isAnnotationPresent(Test.class)) + .filter(testMethodSupplier::isEnabled) + .map(Method::getName).distinct() + .map(name -> String.join(".", token, name)) + .toList()); continue; } @@ -246,7 +249,7 @@ final class TestBuilder implements AutoCloseable { qualifiedMethodName = token; defaultClassName = token.substring(0, lastDotIdx); } else if (defaultClassName == null) { - throw new ParseException("Default class name not found in"); + throw new ParseException("Missing default class name in"); } else { qualifiedMethodName = String.join(".", defaultClassName, token); } @@ -255,155 +258,43 @@ final class TestBuilder implements AutoCloseable { return result.stream(); } - private static boolean filterMethod(String expectedMethodName, Method method) { - if (!method.getName().equals(expectedMethodName)) { - return false; - } - switch (method.getParameterCount()) { - case 0: - return !isParametrized(method); - case 1: - return isParametrized(method); - } - return false; - } - - private static boolean isParametrized(Method method) { - return method.isAnnotationPresent(ParameterGroup.class) || method.isAnnotationPresent( - Parameter.class); - } - - private static List getJavaMethodFromString( - String qualifiedMethodName) { + private List getJavaMethodFromString(String qualifiedMethodName) { int lastDotIdx = qualifiedMethodName.lastIndexOf('.'); if (lastDotIdx == -1) { - throw new ParseException("Class name not found in"); + throw new ParseException("Missing class name in"); } - String className = qualifiedMethodName.substring(0, lastDotIdx); - String methodName = qualifiedMethodName.substring(lastDotIdx + 1); - Class methodClass; + try { - methodClass = Class.forName(className); - } catch (ClassNotFoundException ex) { - throw new ParseException(String.format("Class [%s] not found;", - className)); + return testMethodSupplier.findNullaryLikeMethods( + fromQualifiedMethodName(qualifiedMethodName)); + } catch (NoSuchMethodException ex) { + throw new ParseException(ex.getMessage() + ";", ex); } - // Get the list of all public methods as need to deal with overloads. - List methods = Stream.of(methodClass.getMethods()).filter( - (m) -> filterMethod(methodName, m)).collect(Collectors.toList()); - if (methods.isEmpty()) { - throw new ParseException(String.format( - "Method [%s] not found in [%s] class;", - methodName, className)); - } - - trace(String.format("%s -> %s", qualifiedMethodName, methods)); - return methods; } - private static Stream getJavaMethodsFromArg(String argValue) { - return cmdLineArgValueToMethodNames(argValue).map( - ThrowingFunction.toFunction( - TestBuilder::getJavaMethodFromString)).flatMap( - List::stream).sequential(); + private Stream getJavaMethodsFromArg(String argValue) { + var methods = cmdLineArgValueToMethodNames(argValue) + .map(this::getJavaMethodFromString) + .flatMap(List::stream).toList(); + trace(String.format("%s -> %s", argValue, methods)); + return methods.stream(); } - private static Parameter[] getMethodParameters(Method method) { - if (method.isAnnotationPresent(ParameterGroup.class)) { - return ((ParameterGroup) method.getAnnotation(ParameterGroup.class)).value(); - } - - if (method.isAnnotationPresent(Parameter.class)) { - return new Parameter[]{(Parameter) method.getAnnotation( - Parameter.class)}; - } - - // Unexpected - return null; - } - - private static Stream toCtorArgs(Method method) throws - IllegalAccessException, InvocationTargetException { - Class type = method.getDeclaringClass(); - List paremetersProviders = Stream.of(type.getMethods()) - .filter(m -> m.getParameterCount() == 0) - .filter(m -> (m.getModifiers() & Modifier.STATIC) != 0) - .filter(m -> m.isAnnotationPresent(Parameters.class)) - .sorted() - .collect(Collectors.toList()); - if (paremetersProviders.isEmpty()) { - // Single instance using the default constructor. - return Stream.ofNullable(MethodCall.DEFAULT_CTOR_ARGS); - } - - // Pick the first method from the list. - Method paremetersProvider = paremetersProviders.iterator().next(); - if (paremetersProviders.size() > 1) { - trace(String.format( - "Found %d public static methods without arguments with %s annotation. Will use %s", - paremetersProviders.size(), Parameters.class, - paremetersProvider)); - paremetersProviders.stream().map(Method::toString).forEach( - TestBuilder::trace); - } - - // Construct collection of arguments for test class instances. - return ((Collection) paremetersProvider.invoke(null)).stream(); - } - - private static Stream toMethodCalls(Method method) throws - IllegalAccessException, InvocationTargetException { - return toCtorArgs(method).map(v -> toMethodCalls(v, method)).flatMap( - s -> s).peek(methodCall -> { - // Make sure required constructor is accessible if the one is needed. - // Need to probe all methods as some of them might be static - // and some class members. - // Only class members require ctors. - try { - methodCall.checkRequiredConstructor(); - } catch (NoSuchMethodException ex) { - throw new ParseException(ex.getMessage() + "."); - } - }); - } - - private static Stream toMethodCalls(Object[] ctorArgs, Method method) { - if (!isParametrized(method)) { - return Stream.of(new MethodCall(ctorArgs, method)); - } - Parameter[] annotations = getMethodParameters(method); - if (annotations.length == 0) { - return Stream.of(new MethodCall(ctorArgs, method)); - } - return Stream.of(annotations).map((a) -> { - Class paramType = method.getParameterTypes()[0]; - final Object annotationValue; - if (!paramType.isArray()) { - annotationValue = fromString(a.value()[0], paramType); - } else { - Class paramComponentType = paramType.getComponentType(); - annotationValue = Array.newInstance(paramComponentType, a.value().length); - var idx = new AtomicInteger(-1); - Stream.of(a.value()).map(v -> fromString(v, paramComponentType)).sequential().forEach( - v -> Array.set(annotationValue, idx.incrementAndGet(), v)); + private Stream toMethodCalls(Method method) throws + IllegalAccessException, InvocationTargetException, InvalidAnnotationException { + return testMethodSupplier.mapToMethodCalls(method).peek(methodCall -> { + // Make sure required constructor is accessible if the one is needed. + // Need to probe all methods as some of them might be static + // and some class members. + // Only class members require ctors. + try { + methodCall.checkRequiredConstructor(); + } catch (NoSuchMethodException ex) { + throw new ParseException(ex.getMessage() + ".", ex); } - return new MethodCall(ctorArgs, method, annotationValue); }); } - private static Object fromString(String value, Class toType) { - if (toType.isEnum()) { - return Enum.valueOf(toType, value); - } - Function converter = conv.get(toType); - if (converter == null) { - throw new RuntimeException(String.format( - "Failed to find a conversion of [%s] string to %s type", - value, toType)); - } - return converter.apply(value); - } - // Wraps Method.invike() into ThrowingRunnable.run() private ThrowingConsumer wrap(Method method) { return (test) -> { @@ -427,6 +318,10 @@ final class TestBuilder implements AutoCloseable { super(msg); } + ParseException(String msg, Exception ex) { + super(msg, ex); + } + void setContext(String badCmdLineArg) { this.badCmdLineArg = badCmdLineArg; } @@ -448,8 +343,9 @@ final class TestBuilder implements AutoCloseable { } } + private final TestMethodSupplier testMethodSupplier; private final Map> argProcessors; - private Consumer testConsumer; + private final Consumer testConsumer; private List testGroup; private List beforeActions; private List afterActions; @@ -458,14 +354,5 @@ final class TestBuilder implements AutoCloseable { private String spaceSubstitute; private boolean dryRun; - private final static Map> conv = Map.of( - boolean.class, Boolean::valueOf, - Boolean.class, Boolean::valueOf, - int.class, Integer::valueOf, - Integer.class, Integer::valueOf, - long.class, Long::valueOf, - Long.class, Long::valueOf, - String.class, String::valueOf); - - final static String CMDLINE_ARG_PREFIX = "--jpt-"; + static final String CMDLINE_ARG_PREFIX = "--jpt-"; } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestBuilderConfig.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestBuilderConfig.java new file mode 100644 index 00000000000..baeaec32546 --- /dev/null +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestBuilderConfig.java @@ -0,0 +1,62 @@ +/* + * 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. + */ + +package jdk.jpackage.test; + +import java.util.Objects; +import jdk.internal.util.OperatingSystem; + +final class TestBuilderConfig { + TestBuilderConfig() { + } + + TestMethodSupplier createTestMethodSupplier() { + return new TestMethodSupplier(os); + } + + OperatingSystem getOperatingSystem() { + return os; + } + + static TestBuilderConfig getDefault() { + return DEFAULT.get(); + } + + static void setOperatingSystem(OperatingSystem os) { + Objects.requireNonNull(os); + DEFAULT.get().os = os; + } + + static void setDefaults() { + DEFAULT.set(new TestBuilderConfig()); + } + + private OperatingSystem os = OperatingSystem.current(); + + private static final ThreadLocal DEFAULT = new ThreadLocal<>() { + @Override + protected TestBuilderConfig initialValue() { + return new TestBuilderConfig(); + } + }; +} diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestInstance.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestInstance.java index 105c8f707cd..f619c9e222e 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestInstance.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestInstance.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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,8 +32,10 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -50,7 +52,7 @@ final class TestInstance implements ThrowingRunnable { String testFullName() { StringBuilder sb = new StringBuilder(); - sb.append(clazz.getSimpleName()); + sb.append(clazz.getName()); if (instanceArgs != null) { sb.append('(').append(instanceArgs).append(')'); } @@ -78,12 +80,12 @@ final class TestInstance implements ThrowingRunnable { } Builder ctorArgs(Object... v) { - ctorArgs = ofNullable(v); + ctorArgs = Arrays.asList(v); return this; } Builder methodArgs(Object... v) { - methodArgs = ofNullable(v); + methodArgs = Arrays.asList(v); return this; } @@ -107,22 +109,18 @@ final class TestInstance implements ThrowingRunnable { } return values.stream().map(v -> { if (v != null && v.getClass().isArray()) { - return String.format("%s(length=%d)", - Arrays.deepToString((Object[]) v), - Array.getLength(v)); + String asString; + if (v.getClass().getComponentType().isPrimitive()) { + asString = PRIMITIVE_ARRAY_FORMATTERS.get(v.getClass()).apply(v); + } else { + asString = Arrays.deepToString((Object[]) v); + } + return String.format("%s(length=%d)", asString, Array.getLength(v)); } return String.format("%s", v); }).collect(Collectors.joining(", ")); } - private static List ofNullable(Object... values) { - List result = new ArrayList(); - for (var v: values) { - result.add(v); - } - return result; - } - private List ctorArgs; private List methodArgs; private Method method; @@ -331,7 +329,7 @@ final class TestInstance implements ThrowingRunnable { private final boolean dryRun; private final Path workDir; - private final static Set KEEP_WORK_DIR = Functional.identity( + private static final Set KEEP_WORK_DIR = Functional.identity( () -> { final String propertyName = "keep-work-dir"; Set keepWorkDir = TKit.tokenizeConfigProperty( @@ -355,4 +353,15 @@ final class TestInstance implements ThrowingRunnable { return Collections.unmodifiableSet(result); }).get(); + private static final Map, Function> PRIMITIVE_ARRAY_FORMATTERS = Map.of( + boolean[].class, v -> Arrays.toString((boolean[])v), + byte[].class, v -> Arrays.toString((byte[])v), + char[].class, v -> Arrays.toString((char[])v), + short[].class, v -> Arrays.toString((short[])v), + int[].class, v -> Arrays.toString((int[])v), + long[].class, v -> Arrays.toString((long[])v), + float[].class, v -> Arrays.toString((float[])v), + double[].class, v -> Arrays.toString((double[])v) + ); + } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestMethodSupplier.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestMethodSupplier.java new file mode 100644 index 00000000000..83b1c19bd95 --- /dev/null +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestMethodSupplier.java @@ -0,0 +1,510 @@ +/* + * 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. + */ + +package jdk.jpackage.test; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Executable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import jdk.internal.util.OperatingSystem; +import jdk.jpackage.test.Annotations.Parameter; +import jdk.jpackage.test.Annotations.ParameterGroup; +import jdk.jpackage.test.Annotations.ParameterSupplier; +import jdk.jpackage.test.Annotations.ParameterSupplierGroup; +import jdk.jpackage.test.Annotations.Parameters; +import jdk.jpackage.test.Annotations.Test; +import static jdk.jpackage.test.Functional.ThrowingFunction.toFunction; +import static jdk.jpackage.test.Functional.ThrowingSupplier.toSupplier; +import static jdk.jpackage.test.MethodCall.mapArgs; + +final class TestMethodSupplier { + + TestMethodSupplier(OperatingSystem os) { + Objects.requireNonNull(os); + this.os = os; + } + + record MethodQuery(String className, String methodName) { + + List lookup() throws ClassNotFoundException { + final Class methodClass = Class.forName(className); + + // Get the list of all public methods as need to deal with overloads. + return Stream.of(methodClass.getMethods()).filter(method -> { + return method.getName().equals(methodName); + }).toList(); + } + + static MethodQuery fromQualifiedMethodName(String qualifiedMethodName) { + int lastDotIdx = qualifiedMethodName.lastIndexOf('.'); + if (lastDotIdx == -1) { + throw new IllegalArgumentException("Class name not specified"); + } + + var className = qualifiedMethodName.substring(0, lastDotIdx); + var methodName = qualifiedMethodName.substring(lastDotIdx + 1); + + return new MethodQuery(className, methodName); + } + } + + List findNullaryLikeMethods(MethodQuery query) throws NoSuchMethodException { + List methods; + + try { + methods = query.lookup(); + } catch (ClassNotFoundException ex) { + throw new NoSuchMethodException( + String.format("Class [%s] not found", query.className())); + } + + if (methods.isEmpty()) { + throw new NoSuchMethodException(String.format( + "Public method [%s] not found in [%s] class", + query.methodName(), query.className())); + } + + methods = methods.stream().filter(method -> { + if (isParameterized(method) && isTest(method)) { + // Always accept test method with annotations producing arguments for its invocation. + return true; + } else { + return method.getParameterCount() == 0; + } + }).filter(this::isEnabled).toList(); + + if (methods.isEmpty()) { + throw new NoSuchMethodException(String.format( + "Suitable public method [%s] not found in [%s] class", + query.methodName(), query.className())); + } + + return methods; + } + + boolean isTestClass(Class type) { + var typeStatus = processedTypes.get(type); + if (typeStatus == null) { + typeStatus = Verifier.isTestClass(type) ? TypeStatus.TEST_CLASS : TypeStatus.NOT_TEST_CLASS; + processedTypes.put(type, typeStatus); + } + + return !TypeStatus.NOT_TEST_CLASS.equals(typeStatus); + } + + void verifyTestClass(Class type) throws InvalidAnnotationException { + var typeStatus = processedTypes.get(type); + if (typeStatus == null) { + // The "type" has not been verified yet. + try { + Verifier.verifyTestClass(type); + processedTypes.put(type, TypeStatus.VALID_TEST_CLASS); + return; + } catch (InvalidAnnotationException ex) { + processedTypes.put(type, TypeStatus.TEST_CLASS); + throw ex; + } + } + + switch (typeStatus) { + case NOT_TEST_CLASS -> Verifier.throwNotTestClassException(type); + case TEST_CLASS -> Verifier.verifyTestClass(type); + case VALID_TEST_CLASS -> {} + } + } + + boolean isEnabled(Method method) { + return Stream.of(Test.class, Parameters.class) + .filter(method::isAnnotationPresent) + .findFirst() + .map(method::getAnnotation) + .map(this::canRunOnTheOperatingSystem) + .orElse(true); + } + + Stream mapToMethodCalls(Method method) throws + IllegalAccessException, InvocationTargetException { + return toCtorArgs(method).map(v -> toMethodCalls(v, method)).flatMap(x -> x); + } + + private Stream toCtorArgs(Method method) throws + IllegalAccessException, InvocationTargetException { + + if ((method.getModifiers() & Modifier.STATIC) != 0) { + // Static method, no instance + return Stream.ofNullable(DEFAULT_CTOR_ARGS); + } + + final var type = method.getDeclaringClass(); + + final var paremeterSuppliers = filterParameterSuppliers(type) + .filter(m -> m.isAnnotationPresent(Parameters.class)) + .filter(this::isEnabled) + .sorted(Comparator.comparing(Method::getName)).toList(); + if (paremeterSuppliers.isEmpty()) { + // Single instance using the default constructor. + return Stream.ofNullable(DEFAULT_CTOR_ARGS); + } + + // Construct collection of arguments for test class instances. + return createArgs(paremeterSuppliers.toArray(Method[]::new)); + } + + private Stream toMethodCalls(Object[] ctorArgs, Method method) { + if (!isParameterized(method)) { + return Stream.of(new MethodCall(ctorArgs, method)); + } + + var fromParameter = Stream.of(getMethodParameters(method)).map(a -> { + return createArgsForAnnotation(method, a); + }).flatMap(List::stream); + + var fromParameterSupplier = Stream.of(getMethodParameterSuppliers(method)).map(a -> { + return toSupplier(() -> createArgsForAnnotation(method, a)).get(); + }).flatMap(List::stream); + + return Stream.concat(fromParameter, fromParameterSupplier).map(args -> { + return new MethodCall(ctorArgs, method, args); + }); + } + + private List createArgsForAnnotation(Executable exec, Parameter a) { + if (!canRunOnTheOperatingSystem(a)) { + return List.of(); + } + + final var annotationArgs = a.value(); + final var execParameterTypes = exec.getParameterTypes(); + + if (execParameterTypes.length > annotationArgs.length) { + if (execParameterTypes.length - annotationArgs.length == 1 && exec.isVarArgs()) { + } else { + throw new RuntimeException(String.format( + "Not enough annotation values %s for [%s]", + List.of(annotationArgs), exec)); + } + } + + final Class[] argTypes; + if (exec.isVarArgs()) { + List> argTypesBuilder = new ArrayList<>(); + var lastExecParameterTypeIdx = execParameterTypes.length - 1; + argTypesBuilder.addAll(List.of(execParameterTypes).subList(0, + lastExecParameterTypeIdx)); + argTypesBuilder.addAll(Collections.nCopies( + Integer.max(0, annotationArgs.length - lastExecParameterTypeIdx), + execParameterTypes[lastExecParameterTypeIdx].componentType())); + argTypes = argTypesBuilder.toArray(Class[]::new); + } else { + argTypes = execParameterTypes; + } + + if (argTypes.length < annotationArgs.length) { + throw new RuntimeException(String.format( + "Too many annotation values %s for [%s]", + List.of(annotationArgs), exec)); + } + + var args = mapArgs(exec, IntStream.range(0, argTypes.length).mapToObj(idx -> { + return fromString(annotationArgs[idx], argTypes[idx]); + }).toArray(Object[]::new)); + + return List.of(args); + } + + private List createArgsForAnnotation(Executable exec, + ParameterSupplier a) throws IllegalAccessException, + InvocationTargetException { + if (!canRunOnTheOperatingSystem(a)) { + return List.of(); + } + + final Class execClass = exec.getDeclaringClass(); + final var supplierFuncName = a.value(); + + final MethodQuery methodQuery; + if (!a.value().contains(".")) { + // No class name specified + methodQuery = new MethodQuery(execClass.getName(), a.value()); + } else { + methodQuery = MethodQuery.fromQualifiedMethodName(supplierFuncName); + } + + final Method supplierMethod; + try { + final var parameterSupplierCandidates = findNullaryLikeMethods(methodQuery); + final Function classForName = toFunction(Class::forName); + final var supplierMethodClass = classForName.apply(methodQuery.className()); + if (parameterSupplierCandidates.isEmpty()) { + throw new RuntimeException(String.format( + "No parameter suppliers in [%s] class", + supplierMethodClass.getName())); + } + + var allParameterSuppliers = filterParameterSuppliers(supplierMethodClass).toList(); + + supplierMethod = findNullaryLikeMethods(methodQuery) + .stream() + .filter(allParameterSuppliers::contains) + .findFirst().orElseThrow(() -> { + var msg = String.format( + "No suitable parameter supplier found for %s(%s) annotation", + a, supplierFuncName); + trace(String.format( + "%s. Parameter suppliers of %s class:", msg, + execClass.getName())); + IntStream.range(0, allParameterSuppliers.size()).mapToObj(idx -> { + return String.format(" [%d/%d] %s()", idx + 1, + allParameterSuppliers.size(), + allParameterSuppliers.get(idx).getName()); + }).forEachOrdered(TestMethodSupplier::trace); + + return new RuntimeException(msg); + }); + } catch (NoSuchMethodException ex) { + throw new RuntimeException(String.format( + "Method not found for %s(%s) annotation", a, supplierFuncName)); + } + + return createArgs(supplierMethod).map(args -> { + return mapArgs(exec, args); + }).toList(); + } + + private boolean canRunOnTheOperatingSystem(Annotation a) { + switch (a) { + case Test t -> { + return canRunOnTheOperatingSystem(os, t.ifOS(), t.ifNotOS()); + } + case Parameters t -> { + return canRunOnTheOperatingSystem(os, t.ifOS(), t.ifNotOS()); + } + case Parameter t -> { + return canRunOnTheOperatingSystem(os, t.ifOS(), t.ifNotOS()); + } + case ParameterSupplier t -> { + return canRunOnTheOperatingSystem(os, t.ifOS(), t.ifNotOS()); + } + default -> { + return true; + } + } + } + + private static boolean isParameterized(Method method) { + return Stream.of( + Parameter.class, ParameterGroup.class, + ParameterSupplier.class, ParameterSupplierGroup.class + ).anyMatch(method::isAnnotationPresent); + } + + private static boolean isTest(Method method) { + return method.isAnnotationPresent(Test.class); + } + + private static boolean canRunOnTheOperatingSystem(OperatingSystem value, + OperatingSystem[] include, OperatingSystem[] exclude) { + Set suppordOperatingSystems = new HashSet<>(); + suppordOperatingSystems.addAll(List.of(include)); + suppordOperatingSystems.removeAll(List.of(exclude)); + return suppordOperatingSystems.contains(value); + } + + private static Parameter[] getMethodParameters(Method method) { + if (method.isAnnotationPresent(ParameterGroup.class)) { + return ((ParameterGroup) method.getAnnotation(ParameterGroup.class)).value(); + } + + if (method.isAnnotationPresent(Parameter.class)) { + return new Parameter[]{(Parameter) method.getAnnotation(Parameter.class)}; + } + + return new Parameter[0]; + } + + private static ParameterSupplier[] getMethodParameterSuppliers(Method method) { + if (method.isAnnotationPresent(ParameterSupplierGroup.class)) { + return ((ParameterSupplierGroup) method.getAnnotation(ParameterSupplierGroup.class)).value(); + } + + if (method.isAnnotationPresent(ParameterSupplier.class)) { + return new ParameterSupplier[]{(ParameterSupplier) method.getAnnotation( + ParameterSupplier.class)}; + } + + return new ParameterSupplier[0]; + } + + private static Stream filterParameterSuppliers(Class type) { + return Stream.of(type.getMethods()) + .filter(m -> m.getParameterCount() == 0) + .filter(m -> (m.getModifiers() & Modifier.STATIC) != 0) + .sorted(Comparator.comparing(Method::getName)); + } + + private static Stream createArgs(Method ... parameterSuppliers) throws + IllegalAccessException, InvocationTargetException { + List args = new ArrayList<>(); + for (var parameterSupplier : parameterSuppliers) { + args.addAll((Collection) parameterSupplier.invoke(null)); + } + return args.stream(); + } + + private static Object fromString(String value, Class toType) { + if (toType.isEnum()) { + return Enum.valueOf(toType, value); + } + Function converter = FROM_STRING.get(toType); + if (converter == null) { + throw new RuntimeException(String.format( + "Failed to find a conversion of [%s] string to %s type", + value, toType.getName())); + } + return converter.apply(value); + } + + private static void trace(String msg) { + if (TKit.VERBOSE_TEST_SETUP) { + TKit.log(msg); + } + } + + static class InvalidAnnotationException extends Exception { + InvalidAnnotationException(String msg) { + super(msg); + } + } + + private static class Verifier { + static boolean isTestClass(Class type) { + for (var method : type.getDeclaredMethods()) { + if (isParameterized(method) || isTest(method)) { + return true; + } + } + return false; + } + + static void verifyTestClass(Class type) throws InvalidAnnotationException { + boolean withTestAnnotations = false; + for (var method : type.getDeclaredMethods()) { + if (!withTestAnnotations && (isParameterized(method) || isTest(method))) { + withTestAnnotations = true; + } + verifyAnnotationsCorrect(method); + } + + if (!withTestAnnotations) { + throwNotTestClassException(type); + } + } + + static void throwNotTestClassException(Class type) throws InvalidAnnotationException { + throw new InvalidAnnotationException(String.format( + "Type [%s] is not a test class", type.getName())); + } + + private static void verifyAnnotationsCorrect(Method method) throws + InvalidAnnotationException { + var parameterized = isParameterized(method); + if (parameterized && !isTest(method)) { + throw new InvalidAnnotationException(String.format( + "Missing %s annotation on [%s] method", Test.class.getName(), method)); + } + + var isPublic = Modifier.isPublic(method.getModifiers()); + + if (isTest(method) && !isPublic) { + throw new InvalidAnnotationException(String.format( + "Non-public method [%s] with %s annotation", + method, Test.class.getName())); + } + + if (method.isAnnotationPresent(Parameters.class) && !isPublic) { + throw new InvalidAnnotationException(String.format( + "Non-public method [%s] with %s annotation", + method, Test.class.getName())); + } + } + } + + private enum TypeStatus { + NOT_TEST_CLASS, + TEST_CLASS, + VALID_TEST_CLASS, + } + + private final OperatingSystem os; + private final Map, TypeStatus> processedTypes = new HashMap<>(); + + private static final Object[] DEFAULT_CTOR_ARGS = new Object[0]; + + private static final Map> FROM_STRING; + + static { + Map> primitives = Map.of( + boolean.class, Boolean::valueOf, + byte.class, Byte::valueOf, + short.class, Short::valueOf, + int.class, Integer::valueOf, + long.class, Long::valueOf, + float.class, Float::valueOf, + double.class, Double::valueOf); + + Map> boxed = Map.of( + Boolean.class, Boolean::valueOf, + Byte.class, Byte::valueOf, + Short.class, Short::valueOf, + Integer.class, Integer::valueOf, + Long.class, Long::valueOf, + Float.class, Float::valueOf, + Double.class, Double::valueOf); + + Map> other = Map.of( + String.class, String::valueOf, + Path.class, Path::of); + + Map> combined = new HashMap<>(primitives); + combined.putAll(other); + combined.putAll(boxed); + + FROM_STRING = Collections.unmodifiableMap(combined); + } +} diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java index 338ff7a767b..1976a5cf72c 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java @@ -64,22 +64,6 @@ public class WindowsHelper { return Path.of(cmd.getArgumentValue("--install-dir", cmd::name)); } - // Tests have problems on windows where path in the temp dir are too long - // for the wix tools. We can't use a tempDir outside the TKit's WorkDir, so - // we minimize both the tempRoot directory name (above) and the tempDir name - // (below) to the extension part (which is necessary to differenciate between - // the multiple PackageTypes that will be run for one JPackageCommand). - // It might be beter if the whole work dir name was shortened from: - // jtreg_open_test_jdk_tools_jpackage_share_jdk_jpackage_tests_BasicTest_java. - public static Path getTempDirectory(JPackageCommand cmd, Path tempRoot) { - String ext = cmd.outputBundle().getFileName().toString(); - int i = ext.lastIndexOf("."); - if (i > 0 && i < (ext.length() - 1)) { - ext = ext.substring(i+1); - } - return tempRoot.resolve(ext); - } - private static void runMsiexecWithRetries(Executor misexec) { Executor.Result result = null; for (int attempt = 0; attempt < 8; ++attempt) { diff --git a/test/jdk/tools/jpackage/linux/jdk/jpackage/tests/UsrTreeTest.java b/test/jdk/tools/jpackage/linux/UsrTreeTest.java similarity index 100% rename from test/jdk/tools/jpackage/linux/jdk/jpackage/tests/UsrTreeTest.java rename to test/jdk/tools/jpackage/linux/UsrTreeTest.java diff --git a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/AppVersionTest.java b/test/jdk/tools/jpackage/share/AppVersionTest.java similarity index 98% rename from test/jdk/tools/jpackage/share/jdk/jpackage/tests/AppVersionTest.java rename to test/jdk/tools/jpackage/share/AppVersionTest.java index d4f28ec17be..efd025590d2 100644 --- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/AppVersionTest.java +++ b/test/jdk/tools/jpackage/share/AppVersionTest.java @@ -21,7 +21,6 @@ * questions. */ -package jdk.jpackage.tests; import java.io.IOException; import java.util.Collection; @@ -46,7 +45,7 @@ import org.w3c.dom.Document; * @build jdk.jpackage.test.* * @compile AppVersionTest.java * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.AppVersionTest + * --jpt-run=AppVersionTest */ public final class AppVersionTest { diff --git a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/BasicTest.java b/test/jdk/tools/jpackage/share/BasicTest.java similarity index 95% rename from test/jdk/tools/jpackage/share/jdk/jpackage/tests/BasicTest.java rename to test/jdk/tools/jpackage/share/BasicTest.java index 1222fa95949..61668b9e878 100644 --- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/BasicTest.java +++ b/test/jdk/tools/jpackage/share/BasicTest.java @@ -21,7 +21,6 @@ * questions. */ -package jdk.jpackage.tests; import java.io.IOException; import java.nio.file.Files; @@ -45,8 +44,6 @@ import jdk.jpackage.test.Annotations.Parameter; import jdk.jpackage.test.Functional.ThrowingConsumer; import static jdk.jpackage.test.RunnablePackageTest.Action.CREATE_AND_UNPACK; -import static jdk.jpackage.test.WindowsHelper.getTempDirectory; - /* * @test * @summary jpackage basic testing @@ -54,7 +51,7 @@ import static jdk.jpackage.test.WindowsHelper.getTempDirectory; * @build jdk.jpackage.test.* * @compile BasicTest.java * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.BasicTest + * --jpt-run=BasicTest */ public final class BasicTest { @@ -345,7 +342,7 @@ public final class BasicTest { // Force save of package bundle in test work directory. .addInitializer(JPackageCommand::setDefaultInputOutput) .addInitializer(cmd -> { - Path tempDir = getTempDirectory(cmd, tempRoot); + Path tempDir = tempRoot.resolve(cmd.packageType().name()); switch (type) { case TEMPDIR_EMPTY -> Files.createDirectories(tempDir); case TEMPDIR_NOT_EXIST -> Files.createDirectories(tempDir.getParent()); @@ -362,20 +359,16 @@ public final class BasicTest { if (TestTempType.TEMPDIR_NOT_EMPTY.equals(type)) { pkgTest.setExpectedExitCode(1).addBundleVerifier(cmd -> { // Check jpackage didn't use the supplied directory. - Path tempDir = getTempDirectory(cmd, tempRoot); - String[] tempDirContents = tempDir.toFile().list(); - TKit.assertStringListEquals(List.of("foo.txt"), List.of( - tempDirContents), String.format( - "Check the contents of the supplied temporary directory [%s]", - tempDir)); + Path tempDir = Path.of(cmd.getArgumentValue("--temp")); + TKit.assertDirectoryContent(tempDir).match(Path.of("foo.txt")); TKit.assertStringListEquals(List.of("Hello Duke!"), - Files.readAllLines(tempDir.resolve(tempDirContents[0])), + Files.readAllLines(tempDir.resolve("foo.txt")), "Check the contents of the file in the supplied temporary directory"); }); } else { pkgTest.addBundleVerifier(cmd -> { // Check jpackage used the supplied directory. - Path tempDir = getTempDirectory(cmd, tempRoot); + Path tempDir = Path.of(cmd.getArgumentValue("--temp")); TKit.assertDirectoryNotEmpty(tempDir); }); } diff --git a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/CookedRuntimeTest.java b/test/jdk/tools/jpackage/share/CookedRuntimeTest.java similarity index 98% rename from test/jdk/tools/jpackage/share/jdk/jpackage/tests/CookedRuntimeTest.java rename to test/jdk/tools/jpackage/share/CookedRuntimeTest.java index cfb6ec13ccf..e8bd034bfb4 100644 --- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/CookedRuntimeTest.java +++ b/test/jdk/tools/jpackage/share/CookedRuntimeTest.java @@ -21,7 +21,6 @@ * questions. */ -package jdk.jpackage.tests; import java.io.IOException; import java.nio.file.Files; @@ -46,7 +45,7 @@ import jdk.jpackage.test.TKit; * @build jdk.jpackage.test.* * @compile CookedRuntimeTest.java * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.CookedRuntimeTest + * --jpt-run=CookedRuntimeTest */ public final class CookedRuntimeTest { diff --git a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/DotInNameTest.java b/test/jdk/tools/jpackage/share/DotInNameTest.java similarity index 96% rename from test/jdk/tools/jpackage/share/jdk/jpackage/tests/DotInNameTest.java rename to test/jdk/tools/jpackage/share/DotInNameTest.java index 73be2c2e47a..2617db26183 100644 --- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/DotInNameTest.java +++ b/test/jdk/tools/jpackage/share/DotInNameTest.java @@ -21,7 +21,6 @@ * questions. */ -package jdk.jpackage.tests; import jdk.jpackage.test.JPackageCommand; import jdk.jpackage.test.HelloApp; @@ -37,7 +36,7 @@ import jdk.jpackage.test.Annotations.Test; * @build jdk.jpackage.test.* * @compile DotInNameTest.java * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.DotInNameTest + * --jpt-run=DotInNameTest * --jpt-before-run=jdk.jpackage.test.JPackageCommand.useToolProviderByDefault */ diff --git a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/ErrorTest.java b/test/jdk/tools/jpackage/share/ErrorTest.java similarity index 97% rename from test/jdk/tools/jpackage/share/jdk/jpackage/tests/ErrorTest.java rename to test/jdk/tools/jpackage/share/ErrorTest.java index 57603809607..f012eac1ced 100644 --- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/ErrorTest.java +++ b/test/jdk/tools/jpackage/share/ErrorTest.java @@ -21,7 +21,6 @@ * questions. */ -package jdk.jpackage.tests; import java.util.Collection; import java.util.List; @@ -37,7 +36,7 @@ import jdk.jpackage.test.TKit; * @build jdk.jpackage.test.* * @compile ErrorTest.java * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.ErrorTest + * --jpt-run=ErrorTest * --jpt-before-run=jdk.jpackage.test.JPackageCommand.useExecutableByDefault */ @@ -48,7 +47,7 @@ import jdk.jpackage.test.TKit; * @build jdk.jpackage.test.* * @compile ErrorTest.java * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.ErrorTest + * --jpt-run=ErrorTest * --jpt-before-run=jdk.jpackage.test.JPackageCommand.useToolProviderByDefault */ diff --git a/test/jdk/tools/jpackage/share/InOutPathTest.java b/test/jdk/tools/jpackage/share/InOutPathTest.java index f8cb983bd16..15d96283ef4 100644 --- a/test/jdk/tools/jpackage/share/InOutPathTest.java +++ b/test/jdk/tools/jpackage/share/InOutPathTest.java @@ -29,12 +29,12 @@ import java.util.Collection; import java.util.List; import java.util.Set; import java.util.function.Predicate; -import static java.util.stream.Collectors.toSet; import java.util.stream.Stream; +import jdk.internal.util.OperatingSystem; import jdk.jpackage.internal.AppImageFile; import jdk.jpackage.internal.ApplicationLayout; import jdk.jpackage.internal.PackageFile; -import jdk.jpackage.test.Annotations; +import jdk.jpackage.test.Annotations.Parameters; import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.Functional.ThrowingConsumer; import jdk.jpackage.test.JPackageCommand; @@ -55,47 +55,51 @@ import jdk.jpackage.test.TKit; */ public final class InOutPathTest { - @Annotations.Parameters + @Parameters public static Collection input() { List data = new ArrayList<>(); - for (var packageTypes : List.of(PackageType.IMAGE.toString(), ALL_NATIVE_PACKAGE_TYPES)) { + for (var packageTypeAlias : PackageTypeAlias.values()) { data.addAll(List.of(new Object[][]{ - {packageTypes, wrap(InOutPathTest::outputDirInInputDir, "--dest in --input")}, - {packageTypes, wrap(InOutPathTest::outputDirSameAsInputDir, "--dest same as --input")}, - {packageTypes, wrap(InOutPathTest::tempDirInInputDir, "--temp in --input")}, - {packageTypes, wrap(cmd -> { + {packageTypeAlias, wrap(InOutPathTest::outputDirInInputDir, "--dest in --input")}, + {packageTypeAlias, wrap(InOutPathTest::outputDirSameAsInputDir, "--dest same as --input")}, + {packageTypeAlias, wrap(InOutPathTest::tempDirInInputDir, "--temp in --input")}, + {packageTypeAlias, wrap(cmd -> { outputDirInInputDir(cmd); tempDirInInputDir(cmd); }, "--dest and --temp in --input")}, })); - data.addAll(additionalContentInput(packageTypes, "--app-content")); - } - - if (!TKit.isOSX()) { - data.addAll(List.of(new Object[][]{ - {PackageType.IMAGE.toString(), wrap(cmd -> { - additionalContent(cmd, "--app-content", cmd.outputBundle()); - }, "--app-content same as output bundle")}, - })); - } else { - var contentsFolder = "Contents/MacOS"; - data.addAll(List.of(new Object[][]{ - {PackageType.IMAGE.toString(), wrap(cmd -> { - additionalContent(cmd, "--app-content", cmd.outputBundle().resolve(contentsFolder)); - }, String.format("--app-content same as the \"%s\" folder in the output bundle", contentsFolder))}, - })); - } - - if (TKit.isOSX()) { - data.addAll(additionalContentInput(PackageType.MAC_DMG.toString(), - "--mac-dmg-content")); + data.addAll(additionalContentInput(packageTypeAlias, "--app-content")); } return data; } - private static List additionalContentInput(String packageTypes, String argName) { + @Parameters(ifNotOS = OperatingSystem.MACOS) + public static Collection appContentInputOther() { + return List.of(new Object[][]{ + {PackageTypeAlias.IMAGE, wrap(cmd -> { + additionalContent(cmd, "--app-content", cmd.outputBundle()); + }, "--app-content same as output bundle")}, + }); + } + + @Parameters(ifOS = OperatingSystem.MACOS) + public static Collection appContentInputOSX() { + var contentsFolder = "Contents/MacOS"; + return List.of(new Object[][]{ + {PackageTypeAlias.IMAGE, wrap(cmd -> { + additionalContent(cmd, "--app-content", cmd.outputBundle().resolve(contentsFolder)); + }, String.format("--app-content same as the \"%s\" folder in the output bundle", contentsFolder))}, + }); + } + + @Parameters(ifOS = OperatingSystem.MACOS) + public static Collection inputOSX() { + return List.of(additionalContentInput(PackageType.MAC_DMG, "--mac-dmg-content").toArray(Object[][]::new)); + } + + private static List additionalContentInput(Object packageTypes, String argName) { List data = new ArrayList<>(); data.addAll(List.of(new Object[][]{ @@ -127,13 +131,16 @@ public final class InOutPathTest { return data; } - public InOutPathTest(String packageTypes, Envelope configure) { - if (ALL_NATIVE_PACKAGE_TYPES.equals(packageTypes)) { - this.packageTypes = PackageType.NATIVE; - } else { - this.packageTypes = Stream.of(packageTypes.split(",")).map( - PackageType::valueOf).collect(toSet()); - } + public InOutPathTest(PackageTypeAlias packageTypeAlias, Envelope configure) { + this(packageTypeAlias.packageTypes, configure); + } + + public InOutPathTest(PackageType packageType, Envelope configure) { + this(Set.of(packageType), configure); + } + + public InOutPathTest(Set packageTypes, Envelope configure) { + this.packageTypes = packageTypes; this.configure = configure.value; } @@ -271,6 +278,18 @@ public final class InOutPathTest { } } + private enum PackageTypeAlias { + IMAGE(Set.of(PackageType.IMAGE)), + NATIVE(PackageType.NATIVE), + ; + + PackageTypeAlias(Set packageTypes) { + this.packageTypes = packageTypes; + } + + private final Set packageTypes; + } + private final Set packageTypes; private final ThrowingConsumer configure; @@ -279,6 +298,4 @@ public final class InOutPathTest { // For other platforms it doesn't matter. Keep it the same across // all platforms for simplicity. private static final Path JAR_PATH = Path.of("Resources/duke.jar"); - - private static final String ALL_NATIVE_PACKAGE_TYPES = "NATIVE"; } diff --git a/test/jdk/tools/jpackage/share/InstallDirTest.java b/test/jdk/tools/jpackage/share/InstallDirTest.java index 7047f35e87b..23539589bcc 100644 --- a/test/jdk/tools/jpackage/share/InstallDirTest.java +++ b/test/jdk/tools/jpackage/share/InstallDirTest.java @@ -22,13 +22,12 @@ */ import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; +import jdk.internal.util.OperatingSystem; import jdk.jpackage.test.TKit; import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.PackageType; -import jdk.jpackage.test.Functional; import jdk.jpackage.test.Annotations.Parameter; +import jdk.jpackage.test.Annotations.Test; /** * Test --install-dir parameter. Output of the test should be @@ -76,28 +75,18 @@ import jdk.jpackage.test.Annotations.Parameter; */ public class InstallDirTest { - public static void testCommon() { - final Map INSTALL_DIRS = Functional.identity(() -> { - Map reply = new HashMap<>(); - reply.put(PackageType.WIN_MSI, Path.of("TestVendor\\InstallDirTest1234")); - reply.put(PackageType.WIN_EXE, reply.get(PackageType.WIN_MSI)); - - reply.put(PackageType.LINUX_DEB, Path.of("/opt/jpackage")); - reply.put(PackageType.LINUX_RPM, reply.get(PackageType.LINUX_DEB)); - - reply.put(PackageType.MAC_PKG, Path.of("/Applications/jpackage")); - reply.put(PackageType.MAC_DMG, reply.get(PackageType.MAC_PKG)); - - return reply; - }).get(); - + @Test + @Parameter(value = "TestVendor\\InstallDirTest1234", ifOS = OperatingSystem.WINDOWS) + @Parameter(value = "/opt/jpackage", ifOS = OperatingSystem.LINUX) + @Parameter(value = "/Applications/jpackage", ifOS = OperatingSystem.MACOS) + public static void testCommon(Path installDir) { new PackageTest().configureHelloApp() .addInitializer(cmd -> { - cmd.addArguments("--install-dir", INSTALL_DIRS.get( - cmd.packageType())); + cmd.addArguments("--install-dir", installDir); }).run(); } + @Test(ifOS = OperatingSystem.LINUX) @Parameter("/") @Parameter(".") @Parameter("foo") diff --git a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/JLinkOptionsTest.java b/test/jdk/tools/jpackage/share/JLinkOptionsTest.java similarity index 98% rename from test/jdk/tools/jpackage/share/jdk/jpackage/tests/JLinkOptionsTest.java rename to test/jdk/tools/jpackage/share/JLinkOptionsTest.java index dce60890aba..d4f7bca2ae4 100644 --- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/JLinkOptionsTest.java +++ b/test/jdk/tools/jpackage/share/JLinkOptionsTest.java @@ -21,7 +21,6 @@ * questions. */ -package jdk.jpackage.tests; import java.util.Collection; import java.util.List; @@ -37,7 +36,7 @@ import jdk.jpackage.test.TKit; * @build jdk.jpackage.test.* * @compile JLinkOptionsTest.java * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.JLinkOptionsTest + * --jpt-run=JLinkOptionsTest */ public final class JLinkOptionsTest { diff --git a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/JavaOptionsEqualsTest.java b/test/jdk/tools/jpackage/share/JavaOptionsEqualsTest.java similarity index 96% rename from test/jdk/tools/jpackage/share/jdk/jpackage/tests/JavaOptionsEqualsTest.java rename to test/jdk/tools/jpackage/share/JavaOptionsEqualsTest.java index e1c809dadd3..b1f69c6395f 100644 --- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/JavaOptionsEqualsTest.java +++ b/test/jdk/tools/jpackage/share/JavaOptionsEqualsTest.java @@ -21,7 +21,6 @@ * questions. */ -package jdk.jpackage.tests; import java.util.Collection; import java.util.List; @@ -39,7 +38,7 @@ import jdk.jpackage.test.TKit; * @build jdk.jpackage.test.* * @compile JavaOptionsEqualsTest.java * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.JavaOptionsEqualsTest + * --jpt-run=JavaOptionsEqualsTest * --jpt-before-run=jdk.jpackage.test.JPackageCommand.useExecutableByDefault */ @@ -50,7 +49,7 @@ import jdk.jpackage.test.TKit; * @build jdk.jpackage.test.* * @compile JavaOptionsEqualsTest.java * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.JavaOptionsEqualsTest + * --jpt-run=JavaOptionsEqualsTest * --jpt-before-run=jdk.jpackage.test.JPackageCommand.useToolProviderByDefault */ diff --git a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/JavaOptionsTest.java b/test/jdk/tools/jpackage/share/JavaOptionsTest.java similarity index 98% rename from test/jdk/tools/jpackage/share/jdk/jpackage/tests/JavaOptionsTest.java rename to test/jdk/tools/jpackage/share/JavaOptionsTest.java index cee455271aa..befc90accea 100644 --- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/JavaOptionsTest.java +++ b/test/jdk/tools/jpackage/share/JavaOptionsTest.java @@ -21,7 +21,6 @@ * questions. */ -package jdk.jpackage.tests; import java.util.Collection; import java.util.List; @@ -39,7 +38,7 @@ import jdk.jpackage.test.TKit; * @build jdk.jpackage.test.* * @compile JavaOptionsTest.java * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.JavaOptionsTest + * --jpt-run=JavaOptionsTest * --jpt-before-run=jdk.jpackage.test.JPackageCommand.useToolProviderByDefault */ diff --git a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/MainClassTest.java b/test/jdk/tools/jpackage/share/MainClassTest.java similarity index 96% rename from test/jdk/tools/jpackage/share/jdk/jpackage/tests/MainClassTest.java rename to test/jdk/tools/jpackage/share/MainClassTest.java index 641d0fd00bb..d9188e8f18f 100644 --- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/MainClassTest.java +++ b/test/jdk/tools/jpackage/share/MainClassTest.java @@ -21,7 +21,6 @@ * questions. */ -package jdk.jpackage.tests; import java.io.IOException; import java.nio.file.Files; @@ -46,7 +45,7 @@ import jdk.jpackage.test.Annotations.Parameters; import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.CfgFile; import jdk.jpackage.test.Functional.ThrowingConsumer; -import static jdk.jpackage.tests.MainClassTest.Script.MainClassType.*; + /* @@ -56,7 +55,7 @@ import static jdk.jpackage.tests.MainClassTest.Script.MainClassType.*; * @build jdk.jpackage.test.* * @compile MainClassTest.java * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.MainClassTest + * --jpt-run=MainClassTest */ public final class MainClassTest { @@ -82,7 +81,7 @@ public final class MainClassTest { } Script withJarMainClass(MainClassType v) { - appDesc.setWithMainClass(v != NotSet); + appDesc.setWithMainClass(v != MainClassType.NotSet); jarMainClass = v; return this; } @@ -172,7 +171,8 @@ public final class MainClassTest { @Parameters public static Collection scripts() { - final var withMainClass = Set.of(SetWrong, SetRight); + final var withMainClass = Set.of(Script.MainClassType.SetWrong, + Script.MainClassType.SetRight); List scripts = new ArrayList<>(); for (var withJLink : List.of(true, false)) { @@ -205,7 +205,7 @@ public final class MainClassTest { @Test public void test() throws IOException { - if (script.jarMainClass == SetWrong) { + if (script.jarMainClass == Script.MainClassType.SetWrong) { initJarWithWrongMainClass(); } @@ -224,11 +224,11 @@ public final class MainClassTest { boolean appShouldSucceed = false; // Should succeed if valid main class is set on the command line. - appShouldSucceed |= (script.mainClass == SetRight); + appShouldSucceed |= (script.mainClass == Script.MainClassType.SetRight); // Should succeed if main class is not set on the command line but set // to valid value in the jar. - appShouldSucceed |= (script.mainClass == NotSet && script.jarMainClass == SetRight); + appShouldSucceed |= (script.mainClass == Script.MainClassType.NotSet && script.jarMainClass == Script.MainClassType.SetRight); if (appShouldSucceed) { cmd.executeAndAssertHelloAppImageCreated(); diff --git a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/ModulePathTest.java b/test/jdk/tools/jpackage/share/ModulePathTest.java similarity index 98% rename from test/jdk/tools/jpackage/share/jdk/jpackage/tests/ModulePathTest.java rename to test/jdk/tools/jpackage/share/ModulePathTest.java index c42a24ec310..7d1e86f8ae2 100644 --- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/ModulePathTest.java +++ b/test/jdk/tools/jpackage/share/ModulePathTest.java @@ -21,7 +21,6 @@ * questions. */ -package jdk.jpackage.tests; import java.io.File; import java.io.IOException; @@ -48,7 +47,7 @@ import jdk.jpackage.test.Annotations.Test; * @build jdk.jpackage.test.* * @compile ModulePathTest.java * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.ModulePathTest + * --jpt-run=ModulePathTest */ public final class ModulePathTest { diff --git a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/ModulePathTest2.java b/test/jdk/tools/jpackage/share/ModulePathTest2.java similarity index 97% rename from test/jdk/tools/jpackage/share/jdk/jpackage/tests/ModulePathTest2.java rename to test/jdk/tools/jpackage/share/ModulePathTest2.java index 7b1ec2f5b63..34144907bdb 100644 --- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/ModulePathTest2.java +++ b/test/jdk/tools/jpackage/share/ModulePathTest2.java @@ -21,7 +21,6 @@ * questions. */ -package jdk.jpackage.tests; import java.io.IOException; import java.nio.file.Path; @@ -40,7 +39,7 @@ import jdk.jpackage.test.TKit; * @build jdk.jpackage.test.* * @compile ModulePathTest2.java * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.ModulePathTest2 + * --jpt-run=ModulePathTest2 */ public final class ModulePathTest2 { diff --git a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/ModulePathTest3.java b/test/jdk/tools/jpackage/share/ModulePathTest3.java similarity index 98% rename from test/jdk/tools/jpackage/share/jdk/jpackage/tests/ModulePathTest3.java rename to test/jdk/tools/jpackage/share/ModulePathTest3.java index d6fca043920..b4b51e1adfc 100644 --- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/ModulePathTest3.java +++ b/test/jdk/tools/jpackage/share/ModulePathTest3.java @@ -21,7 +21,6 @@ * questions. */ -package jdk.jpackage.tests; import java.io.IOException; import java.nio.file.Files; @@ -53,7 +52,7 @@ import org.w3c.dom.Document; * @build jdk.jpackage.test.* * @compile ModulePathTest3.java * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.ModulePathTest3 + * --jpt-run=ModulePathTest3 */ public final class ModulePathTest3 { diff --git a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/MultipleJarAppTest.java b/test/jdk/tools/jpackage/share/MultipleJarAppTest.java similarity index 96% rename from test/jdk/tools/jpackage/share/jdk/jpackage/tests/MultipleJarAppTest.java rename to test/jdk/tools/jpackage/share/MultipleJarAppTest.java index 38878929006..0726b131d69 100644 --- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/MultipleJarAppTest.java +++ b/test/jdk/tools/jpackage/share/MultipleJarAppTest.java @@ -21,7 +21,6 @@ * questions. */ -package jdk.jpackage.tests; import java.nio.file.Path; import jdk.jpackage.test.Annotations.Test; @@ -37,7 +36,7 @@ import jdk.jpackage.test.JPackageCommand; * @build jdk.jpackage.test.* * @compile MultipleJarAppTest.java * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.MultipleJarAppTest + * --jpt-run=MultipleJarAppTest */ public final class MultipleJarAppTest { diff --git a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/NoMPathRuntimeTest.java b/test/jdk/tools/jpackage/share/NoMPathRuntimeTest.java similarity index 98% rename from test/jdk/tools/jpackage/share/jdk/jpackage/tests/NoMPathRuntimeTest.java rename to test/jdk/tools/jpackage/share/NoMPathRuntimeTest.java index 4a9aef7860d..37319971183 100644 --- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/NoMPathRuntimeTest.java +++ b/test/jdk/tools/jpackage/share/NoMPathRuntimeTest.java @@ -21,7 +21,6 @@ * questions. */ -package jdk.jpackage.tests; import java.io.IOException; import java.nio.file.Files; @@ -47,7 +46,7 @@ import jdk.jpackage.test.HelloApp; * @build jdk.jpackage.test.* * @compile NoMPathRuntimeTest.java * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.NoMPathRuntimeTest + * --jpt-run=NoMPathRuntimeTest */ public final class NoMPathRuntimeTest { diff --git a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/NonExistentTest.java b/test/jdk/tools/jpackage/share/NonExistentTest.java similarity index 97% rename from test/jdk/tools/jpackage/share/jdk/jpackage/tests/NonExistentTest.java rename to test/jdk/tools/jpackage/share/NonExistentTest.java index 8ca18af356c..4d50f2a5850 100644 --- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/NonExistentTest.java +++ b/test/jdk/tools/jpackage/share/NonExistentTest.java @@ -21,7 +21,6 @@ * questions. */ -package jdk.jpackage.tests; import java.util.Collection; import java.util.List; @@ -37,7 +36,7 @@ import jdk.jpackage.test.TKit; * @build jdk.jpackage.test.* * @compile NonExistentTest.java * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.NonExistentTest + * --jpt-run=NonExistentTest */ public final class NonExistentTest { diff --git a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/PredefinedAppImageErrorTest.java b/test/jdk/tools/jpackage/share/PredefinedAppImageErrorTest.java similarity index 96% rename from test/jdk/tools/jpackage/share/jdk/jpackage/tests/PredefinedAppImageErrorTest.java rename to test/jdk/tools/jpackage/share/PredefinedAppImageErrorTest.java index b01a78120db..bdb4f6bfbd7 100644 --- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/PredefinedAppImageErrorTest.java +++ b/test/jdk/tools/jpackage/share/PredefinedAppImageErrorTest.java @@ -21,7 +21,6 @@ * questions. */ -package jdk.jpackage.tests; import java.io.IOException; import java.nio.file.Path; @@ -42,11 +41,11 @@ import jdk.jpackage.test.TKit; * @compile PredefinedAppImageErrorTest.java * * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.PredefinedAppImageErrorTest + * --jpt-run=PredefinedAppImageErrorTest * --jpt-before-run=jdk.jpackage.test.JPackageCommand.useExecutableByDefault * * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.PredefinedAppImageErrorTest + * --jpt-run=PredefinedAppImageErrorTest * --jpt-before-run=jdk.jpackage.test.JPackageCommand.useToolProviderByDefault */ diff --git a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/UnicodeArgsTest.java b/test/jdk/tools/jpackage/share/UnicodeArgsTest.java similarity index 97% rename from test/jdk/tools/jpackage/share/jdk/jpackage/tests/UnicodeArgsTest.java rename to test/jdk/tools/jpackage/share/UnicodeArgsTest.java index 29722086eec..6fa2717d15e 100644 --- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/UnicodeArgsTest.java +++ b/test/jdk/tools/jpackage/share/UnicodeArgsTest.java @@ -21,7 +21,6 @@ * questions. */ -package jdk.jpackage.tests; import java.util.stream.Collectors; import jdk.jpackage.test.TKit; @@ -38,7 +37,7 @@ import jdk.jpackage.test.JPackageCommand; * @compile UnicodeArgsTest.java * @requires (os.family == "windows") * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.UnicodeArgsTest + * --jpt-run=UnicodeArgsTest */ public final class UnicodeArgsTest { diff --git a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/VendorTest.java b/test/jdk/tools/jpackage/share/VendorTest.java similarity index 96% rename from test/jdk/tools/jpackage/share/jdk/jpackage/tests/VendorTest.java rename to test/jdk/tools/jpackage/share/VendorTest.java index b6e8f44dbf8..4c3a0ffcee2 100644 --- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/VendorTest.java +++ b/test/jdk/tools/jpackage/share/VendorTest.java @@ -21,7 +21,6 @@ * questions. */ -package jdk.jpackage.tests; import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.PackageType; @@ -61,7 +60,7 @@ import jdk.jpackage.test.Annotations.Test; * @build jdk.jpackage.test.* * @compile VendorTest.java * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.VendorTest + * --jpt-run=VendorTest */ /* @@ -74,7 +73,7 @@ import jdk.jpackage.test.Annotations.Test; * @build jdk.jpackage.test.* * @compile VendorTest.java * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=jdk.jpackage.tests.VendorTest + * --jpt-run=VendorTest */ public class VendorTest { diff --git a/test/jdk/tools/jpackage/windows/WinL10nTest.java b/test/jdk/tools/jpackage/windows/WinL10nTest.java index d951b6f8832..814b6401f47 100644 --- a/test/jdk/tools/jpackage/windows/WinL10nTest.java +++ b/test/jdk/tools/jpackage/windows/WinL10nTest.java @@ -22,7 +22,6 @@ */ import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import jdk.jpackage.test.TKit; import jdk.jpackage.test.PackageTest; @@ -37,8 +36,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.jpackage.test.Executor; -import static jdk.jpackage.test.WindowsHelper.getTempDirectory; - /* * @test * @summary Custom l10n of msi installers in jpackage @@ -182,9 +179,8 @@ public class WinL10nTest { } // Preserve config dir to check the set of copied l10n files. - Path tempDir = getTempDirectory(cmd, tempRoot); - Files.createDirectories(tempDir.getParent()); - cmd.addArguments("--temp", tempDir.toString()); + Path tempDir = tempRoot.resolve(cmd.packageType().name()); + cmd.addArguments("--temp", tempDir); }) .addBundleVerifier((cmd, result) -> { if (expectedCultures != null) { @@ -213,7 +209,7 @@ public class WinL10nTest { v.createCmdOutputVerifier(wixSrcDir).apply(getBuildCommandLine(result)); } } - Path tempDir = getTempDirectory(cmd, tempRoot).toAbsolutePath(); + var tempDir = Path.of(cmd.getArgumentValue("--temp")).toAbsolutePath(); for (var v : createDefaultL10nFilesLocVerifiers(tempDir)) { v.apply(getBuildCommandLine(result)); } diff --git a/test/jdk/tools/sincechecker/modules/jdk.charsets/JdkCharsetsCheckSince.java b/test/jdk/tools/sincechecker/modules/jdk.charsets/JdkCharsetsCheckSince.java new file mode 100644 index 00000000000..b9b47998ace --- /dev/null +++ b/test/jdk/tools/sincechecker/modules/jdk.charsets/JdkCharsetsCheckSince.java @@ -0,0 +1,30 @@ +/* + * 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 + * @bug 8343777 + * @summary Test for `@since` declaration in the module-info.java file of jdk.charsets + * @library /test/lib /test/jdk/tools/sincechecker + * @run main SinceChecker jdk.charsets + */ diff --git a/test/jdk/tools/sincechecker/modules/jdk.localedata/JdkLocaledataCheckSince.java b/test/jdk/tools/sincechecker/modules/jdk.localedata/JdkLocaledataCheckSince.java new file mode 100644 index 00000000000..209333b2541 --- /dev/null +++ b/test/jdk/tools/sincechecker/modules/jdk.localedata/JdkLocaledataCheckSince.java @@ -0,0 +1,30 @@ +/* + * 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 + * @bug 8343777 + * @summary Test for `@since` declaration in the module-info.java file of jdk.localedata + * @library /test/lib /test/jdk/tools/sincechecker + * @run main SinceChecker jdk.localedata + */ diff --git a/test/jtreg-ext/requires/VMProps.java b/test/jtreg-ext/requires/VMProps.java index 775b95959c6..5544ad1bebd 100644 --- a/test/jtreg-ext/requires/VMProps.java +++ b/test/jtreg-ext/requires/VMProps.java @@ -122,6 +122,7 @@ public class VMProps implements Callable> { // vm.cds is true if the VM is compiled with cds support. map.put("vm.cds", this::vmCDS); map.put("vm.cds.custom.loaders", this::vmCDSForCustomLoaders); + map.put("vm.cds.supports.aot.class.linking", this::vmCDSSupportsAOTClassLinking); map.put("vm.cds.write.archived.java.heap", this::vmCDSCanWriteArchivedJavaHeap); map.put("vm.continuations", this::vmContinuations); // vm.graal.enabled is true if Graal is used as JIT @@ -457,6 +458,14 @@ public class VMProps implements Callable> { && isCDSRuntimeOptionsCompatible()); } + /** + * @return true if this VM can support the -XX:AOTClassLinking option + */ + protected String vmCDSSupportsAOTClassLinking() { + // Currently, the VM supports AOTClassLinking as long as it's able to write archived java heap. + return vmCDSCanWriteArchivedJavaHeap(); + } + /** * @return true if the VM options specified via the "test.cds.runtime.options" * property is compatible with writing Java heap objects into the CDS archive diff --git a/test/lib/jdk/test/lib/cds/CDSAppTester.java b/test/lib/jdk/test/lib/cds/CDSAppTester.java index c39e6bb8e94..d83c3c36e41 100644 --- a/test/lib/jdk/test/lib/cds/CDSAppTester.java +++ b/test/lib/jdk/test/lib/cds/CDSAppTester.java @@ -28,6 +28,7 @@ import jdk.test.lib.cds.CDSTestUtils; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.StringArrayUtils; +import jtreg.SkippedException; /* * This is a base class used for testing CDS functionalities with complex applications. @@ -42,9 +43,13 @@ abstract public class CDSAppTester { private final String staticArchiveFileLog; private final String dynamicArchiveFile; private final String dynamicArchiveFileLog; - private final String productionRunLog; + private int numProductionRuns = 0; public CDSAppTester(String name) { + if (CDSTestUtils.DYNAMIC_DUMP) { + throw new SkippedException("Tests based on CDSAppTester should be excluded when -Dtest.dynamic.cds.archive is specified"); + } + // Old workflow this.name = name; classListFile = name() + ".classlist"; @@ -53,7 +58,14 @@ abstract public class CDSAppTester { staticArchiveFileLog = staticArchiveFile + ".log"; dynamicArchiveFile = name() + ".dynamic.jsa"; dynamicArchiveFileLog = dynamicArchiveFile + ".log"; - productionRunLog = name() + ".production.log"; + } + + private String productionRunLog() { + if (numProductionRuns == 0) { + return name() + ".production.log"; + } else { + return name() + ".production." + numProductionRuns + ".log"; + } } private enum Workflow { @@ -97,6 +109,11 @@ abstract public class CDSAppTester { public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception {} private Workflow workflow; + private boolean checkExitValue = true; + + public final void setCheckExitValue(boolean b) { + checkExitValue = b; + } public final boolean isStaticWorkflow() { return workflow == Workflow.STATIC; @@ -134,7 +151,10 @@ abstract public class CDSAppTester { for (String logFile : logFiles) { listOutputFile(logFile); } - output.shouldHaveExitValue(0); + if (checkExitValue) { + output.shouldHaveExitValue(0); + } + output.shouldNotContain(CDSTestUtils.MSG_STATIC_FIELD_MAY_HOLD_DIFFERENT_VALUE); CDSTestUtils.checkCommonExecExceptions(output); checkExecution(output, runMode); return output; @@ -189,10 +209,22 @@ abstract public class CDSAppTester { } private OutputAnalyzer productionRun() throws Exception { + return productionRun(null, null); + } + + public OutputAnalyzer productionRun(String[] extraVmArgs) throws Exception { + return productionRun(extraVmArgs, null); + } + + // After calling run(String[]), you can call this method to run the app again, with the AOTCache + // using different args to the VM and application. + public OutputAnalyzer productionRun(String[] extraVmArgs, String[] extraAppArgs) throws Exception { RunMode runMode = RunMode.PRODUCTION; String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode), + "-XX:+UnlockDiagnosticVMOptions", + "-XX:VerifyArchivedFields=2", // make sure archived heap objects are good. "-cp", classpath(runMode), - logToFile(productionRunLog, "cds")); + logToFile(productionRunLog(), "cds")); if (isStaticWorkflow()) { cmdLine = StringArrayUtils.concat(cmdLine, "-XX:SharedArchiveFile=" + staticArchiveFile); @@ -200,8 +232,19 @@ abstract public class CDSAppTester { cmdLine = StringArrayUtils.concat(cmdLine, "-XX:SharedArchiveFile=" + dynamicArchiveFile); } + if (extraVmArgs != null) { + cmdLine = StringArrayUtils.concat(cmdLine, extraVmArgs); + } + cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode)); - return executeAndCheck(cmdLine, runMode, productionRunLog); + + if (extraAppArgs != null) { + cmdLine = StringArrayUtils.concat(cmdLine, extraAppArgs); + } + + OutputAnalyzer out = executeAndCheck(cmdLine, runMode, productionRunLog()); + numProductionRuns ++; + return out; } public void run(String args[]) throws Exception { diff --git a/test/lib/jdk/test/lib/cds/CDSTestUtils.java b/test/lib/jdk/test/lib/cds/CDSTestUtils.java index 7115912380a..a913f2f9fa2 100644 --- a/test/lib/jdk/test/lib/cds/CDSTestUtils.java +++ b/test/lib/jdk/test/lib/cds/CDSTestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -51,6 +51,8 @@ public class CDSTestUtils { "Unable to allocate region, java heap range is already in use."; public static final String MSG_DYNAMIC_NOT_SUPPORTED = "-XX:ArchiveClassesAtExit is unsupported when base CDS archive is not loaded"; + public static final String MSG_STATIC_FIELD_MAY_HOLD_DIFFERENT_VALUE = + "an object points to a static field that may hold a different value at runtime"; public static final boolean DYNAMIC_DUMP = Boolean.getBoolean("test.dynamic.cds.archive"); public interface Checker { @@ -284,6 +286,7 @@ public class CDSTestUtils { output.shouldContain("Written dynamic archive 0x"); } output.shouldHaveExitValue(0); + output.shouldNotContain(MSG_STATIC_FIELD_MAY_HOLD_DIFFERENT_VALUE); for (String match : extraMatches) { output.shouldContain(match); @@ -296,6 +299,7 @@ public class CDSTestUtils { public static OutputAnalyzer checkBaseDump(OutputAnalyzer output) throws Exception { output.shouldContain("Loading classes to share"); output.shouldHaveExitValue(0); + output.shouldNotContain(MSG_STATIC_FIELD_MAY_HOLD_DIFFERENT_VALUE); return output; } @@ -859,4 +863,24 @@ public class CDSTestUtils { Files.copy(srcPath, destPath, REPLACE_EXISTING, COPY_ATTRIBUTES); return destPath; } + + // Some tests were initially written without the knowledge of -XX:+AOTClassLinking. These tests need to + // be adjusted if -XX:+AOTClassLinking is specified in jtreg -vmoptions or -javaoptions: + public static boolean isAOTClassLinkingEnabled() { + return isBooleanVMOptionEnabledInCommandLine("AOTClassLinking"); + } + + public static boolean isBooleanVMOptionEnabledInCommandLine(String optionName) { + String lastMatch = null; + String pattern = "^-XX:." + optionName + "$"; + for (String s : Utils.getTestJavaOpts()) { + if (s.matches(pattern)) { + lastMatch = s; + } + } + if (lastMatch != null && lastMatch.equals("-XX:+" + optionName)) { + return true; + } + return false; + } }