diff --git a/make/Images.gmk b/make/Images.gmk index 10fc8041325c7..acdc594b0097e 100644 --- a/make/Images.gmk +++ b/make/Images.gmk @@ -96,6 +96,10 @@ JLINK_DISABLE_WARNINGS := | ( $(GREP) -v -e "WARNING: Using incubator module" || JDK_IMAGE_SUPPORT_DIR := $(SUPPORT_OUTPUTDIR)/images/jdk JRE_IMAGE_SUPPORT_DIR := $(SUPPORT_OUTPUTDIR)/images/jre +ifeq ($(JLINK_PRODUCE_LINKABLE_RUNTIME), true) + JLINK_JDK_EXTRA_OPTS += --generate-linkable-runtime +endif + $(eval $(call SetupExecute, jlink_jdk, \ WARN := Creating jdk image, \ DEPS := $(JDK_JMODS) $(BASE_RELEASE_FILE) \ diff --git a/make/autoconf/jdk-options.m4 b/make/autoconf/jdk-options.m4 index fec0a93161b44..cf8a856de96d2 100644 --- a/make/autoconf/jdk-options.m4 +++ b/make/autoconf/jdk-options.m4 @@ -586,13 +586,42 @@ AC_DEFUN_ONCE([JDKOPT_SETUP_JMOD_OPTIONS], ################################################################################ # # jlink options. -# We always keep packaged modules in JDK image. # AC_DEFUN_ONCE([JDKOPT_SETUP_JLINK_OPTIONS], [ - UTIL_ARG_ENABLE(NAME: keep-packaged-modules, DEFAULT: true, + + ################################################################################ + # + # Configure option for building a JDK that is suitable for linking from the + # run-time image without JMODs. + # + # Determines whether or not a suitable run-time image is being produced from + # packaged modules. If set to 'true, changes the *default* of packaged + # modules to 'false'. + # + UTIL_ARG_ENABLE(NAME: linkable-runtime, DEFAULT: false, + RESULT: JLINK_PRODUCE_LINKABLE_RUNTIME, + DESC: [enable a JDK build suitable for linking from the run-time image], + CHECKING_MSG: [whether or not a JDK suitable for linking from the run-time image should be produced]) + AC_SUBST(JLINK_PRODUCE_LINKABLE_RUNTIME) + + if test "x$JLINK_PRODUCE_LINKABLE_RUNTIME" = xtrue; then + DEFAULT_PACKAGED_MODULES=false + else + DEFAULT_PACKAGED_MODULES=true + fi + + ################################################################################ + # + # Configure option for packaged modules + # + # We keep packaged modules in the JDK image unless --enable-linkable-runtime is + # requested. + # + UTIL_ARG_ENABLE(NAME: keep-packaged-modules, DEFAULT: $DEFAULT_PACKAGED_MODULES, RESULT: JLINK_KEEP_PACKAGED_MODULES, DESC: [enable keeping of packaged modules in jdk image], + DEFAULT_DESC: [enabled by default unless --enable-linkable-runtime is set], CHECKING_MSG: [if packaged modules are kept]) AC_SUBST(JLINK_KEEP_PACKAGED_MODULES) ]) @@ -638,14 +667,11 @@ AC_DEFUN([JDKOPT_EXCLUDE_TRANSLATIONS], ################################################################################ # -# Optionally disable man pages +# Optionally disable man pages (deprecated) # AC_DEFUN([JDKOPT_ENABLE_DISABLE_MANPAGES], [ - UTIL_ARG_ENABLE(NAME: manpages, DEFAULT: true, RESULT: BUILD_MANPAGES, - DESC: [enable copying of static man pages], - CHECKING_MSG: [if static man pages should be copied]) - AC_SUBST(BUILD_MANPAGES) + UTIL_DEPRECATED_ARG_ENABLE(manpages) ]) ################################################################################ diff --git a/make/autoconf/spec.gmk.template b/make/autoconf/spec.gmk.template index 62afd6577aba5..bcd54058c28fc 100644 --- a/make/autoconf/spec.gmk.template +++ b/make/autoconf/spec.gmk.template @@ -367,8 +367,6 @@ ENABLE_GENERATE_CLASSLIST := @ENABLE_GENERATE_CLASSLIST@ EXCLUDE_TRANSLATIONS := @EXCLUDE_TRANSLATIONS@ -BUILD_MANPAGES := @BUILD_MANPAGES@ - BUILD_CDS_ARCHIVE := @BUILD_CDS_ARCHIVE@ BUILD_CDS_ARCHIVE_COH := @BUILD_CDS_ARCHIVE_COH@ @@ -708,6 +706,7 @@ NEW_JAVADOC = $(INTERIM_LANGTOOLS_ARGS) $(JAVADOC_MAIN_CLASS) JMOD_COMPRESS := @JMOD_COMPRESS@ JLINK_KEEP_PACKAGED_MODULES := @JLINK_KEEP_PACKAGED_MODULES@ +JLINK_PRODUCE_LINKABLE_RUNTIME := @JLINK_PRODUCE_LINKABLE_RUNTIME@ RCFLAGS := @RCFLAGS@ diff --git a/make/common/modules/LauncherCommon.gmk b/make/common/modules/LauncherCommon.gmk index 7c2cef58835c2..aa721a683d7bd 100644 --- a/make/common/modules/LauncherCommon.gmk +++ b/make/common/modules/LauncherCommon.gmk @@ -228,14 +228,11 @@ ifeq ($(call isTargetOsType, unix)+$(MAKEFILE_PREFIX), true+Launcher) endif else # No markdown man pages present - ifeq ($(BUILD_MANPAGES), true) - # BUILD_MANPAGES is a mis-nomer. It really means "copy the pre-generated man pages". - $(eval $(call SetupCopyFiles, COPY_MAN_PAGES, \ - DEST := $(SUPPORT_OUTPUTDIR)/modules_man/$(MODULE)/man1, \ - FILES := $(MAN_FILES_TROFF), \ - )) + $(eval $(call SetupCopyFiles, COPY_MAN_PAGES, \ + DEST := $(SUPPORT_OUTPUTDIR)/modules_man/$(MODULE)/man1, \ + FILES := $(MAN_FILES_TROFF), \ + )) - TARGETS += $(COPY_MAN_PAGES) - endif + TARGETS += $(COPY_MAN_PAGES) endif endif diff --git a/make/conf/jib-profiles.js b/make/conf/jib-profiles.js index 0785d340f48b6..a28d85c146f16 100644 --- a/make/conf/jib-profiles.js +++ b/make/conf/jib-profiles.js @@ -252,7 +252,6 @@ var getJibProfilesCommon = function (input, data) { default_make_targets: ["product-bundles", "test-bundles", "static-libs-bundles"], configure_args: concat( "--with-exclude-translations=es,fr,it,ko,pt_BR,sv,ca,tr,cs,sk,ja_JP_A,ja_JP_HA,ja_JP_HI,ja_JP_I,zh_TW,zh_HK", - "--disable-manpages", "--disable-jvm-feature-shenandoahgc", versionArgs(input, common)) }; diff --git a/src/hotspot/cpu/s390/assembler_s390.hpp b/src/hotspot/cpu/s390/assembler_s390.hpp index ab09460bd8647..c98c100a06842 100644 --- a/src/hotspot/cpu/s390/assembler_s390.hpp +++ b/src/hotspot/cpu/s390/assembler_s390.hpp @@ -56,24 +56,23 @@ class Immediate { } // Test if x is within signed immediate range for nbits. - static bool is_uimm(int64_t x, unsigned int nbits) { + static bool is_uimm(uint64_t x, unsigned int nbits) { // nbits == 0 --> false // nbits >= 64 --> true assert(1 <= nbits && nbits < 64, "don't call, use statically known result"); - const uint64_t xu = (unsigned long)x; const uint64_t maxplus1 = 1UL << nbits; - return xu < maxplus1; // Unsigned comparison. Negative inputs appear to be very large. + return x < maxplus1; // Unsigned comparison. Negative inputs appear to be very large. } - static bool is_uimm32(int64_t x) { + static bool is_uimm32(uint64_t x) { return is_uimm(x, 32); } - static bool is_uimm16(int64_t x) { + static bool is_uimm16(uint64_t x) { return is_uimm(x, 16); } - static bool is_uimm12(int64_t x) { + static bool is_uimm12(uint64_t x) { return is_uimm(x, 12); } - static bool is_uimm8(int64_t x) { + static bool is_uimm8(uint64_t x) { return is_uimm(x, 8); } }; diff --git a/src/hotspot/share/c1/c1_Instruction.hpp b/src/hotspot/share/c1/c1_Instruction.hpp index e577e7fd90bad..e950afc981d19 100644 --- a/src/hotspot/share/c1/c1_Instruction.hpp +++ b/src/hotspot/share/c1/c1_Instruction.hpp @@ -346,11 +346,8 @@ class Instruction: public CompilationResourceObj { NeedsNullCheckFlag = 0, CanTrapFlag, DirectCompareFlag, - IsEliminatedFlag, IsSafepointFlag, IsStaticFlag, - NeedsStoreCheckFlag, - NeedsWriteBarrierFlag, PreservesStateFlag, TargetIsFinalFlag, TargetIsLoadedFlag, @@ -361,7 +358,6 @@ class Instruction: public CompilationResourceObj { ProfileMDOFlag, IsLinkedInBlockFlag, NeedsRangeCheckFlag, - InWorkListFlag, DeoptimizeOnException, KillsMemoryFlag, OmitChecksFlag, @@ -840,14 +836,12 @@ LEAF(StoreField, AccessField) : AccessField(obj, offset, field, is_static, state_before, needs_patching) , _value(value) { - set_flag(NeedsWriteBarrierFlag, as_ValueType(field_type())->is_object()); ASSERT_VALUES pin(); } // accessors Value value() const { return _value; } - bool needs_write_barrier() const { return check_flag(NeedsWriteBarrierFlag); } // generic virtual void input_values_do(ValueVisitor* f) { AccessField::input_values_do(f); f->visit(&_value); } @@ -974,16 +968,12 @@ LEAF(StoreIndexed, AccessIndexed) : AccessIndexed(array, index, length, elt_type, state_before, mismatched) , _value(value), _profiled_method(nullptr), _profiled_bci(0), _check_boolean(check_boolean) { - set_flag(NeedsWriteBarrierFlag, (as_ValueType(elt_type)->is_object())); - set_flag(NeedsStoreCheckFlag, (as_ValueType(elt_type)->is_object())); ASSERT_VALUES pin(); } // accessors Value value() const { return _value; } - bool needs_write_barrier() const { return check_flag(NeedsWriteBarrierFlag); } - bool needs_store_check() const { return check_flag(NeedsStoreCheckFlag); } bool check_boolean() const { return _check_boolean; } // Helpers for MethodData* profiling void set_should_profile(bool value) { set_flag(ProfileMDOFlag, value); } diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index 8321c3efb9c98..19e7fc1d2bd43 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp @@ -128,73 +128,84 @@ ParallelCompactData::RegionData::dc_claimed = 0x8U << dc_shift; const ParallelCompactData::RegionData::region_sz_t ParallelCompactData::RegionData::dc_completed = 0xcU << dc_shift; +bool ParallelCompactData::RegionData::is_clear() { + return (_destination == nullptr) && + (_source_region == 0) && + (_partial_obj_addr == nullptr) && + (_partial_obj_size == 0) && + (_dc_and_los == 0) && + (_shadow_state == 0); +} + +#ifdef ASSERT +void ParallelCompactData::RegionData::verify_clear() { + assert(_destination == nullptr, "inv"); + assert(_source_region == 0, "inv"); + assert(_partial_obj_addr == nullptr, "inv"); + assert(_partial_obj_size == 0, "inv"); + assert(_dc_and_los == 0, "inv"); + assert(_shadow_state == 0, "inv"); +} +#endif + SpaceInfo PSParallelCompact::_space_info[PSParallelCompact::last_space_id]; SpanSubjectToDiscoveryClosure PSParallelCompact::_span_based_discoverer; ReferenceProcessor* PSParallelCompact::_ref_processor = nullptr; -void SplitInfo::record(size_t src_region_idx, size_t partial_obj_size, - HeapWord* destination) -{ - assert(src_region_idx != 0, "invalid src_region_idx"); - assert(partial_obj_size != 0, "invalid partial_obj_size argument"); - assert(destination != nullptr, "invalid destination argument"); +void SplitInfo::record(size_t split_region_idx, HeapWord* split_point, size_t preceding_live_words) { + assert(split_region_idx != 0, "precondition"); - _src_region_idx = src_region_idx; - _partial_obj_size = partial_obj_size; - _destination = destination; + // Obj denoted by split_point will be deferred to the next space. + assert(split_point != nullptr, "precondition"); - // These fields may not be updated below, so make sure they're clear. - assert(_dest_region_addr == nullptr, "should have been cleared"); - assert(_first_src_addr == nullptr, "should have been cleared"); - - // Determine the number of destination regions for the partial object. - HeapWord* const last_word = destination + partial_obj_size - 1; const ParallelCompactData& sd = PSParallelCompact::summary_data(); - HeapWord* const beg_region_addr = sd.region_align_down(destination); - HeapWord* const end_region_addr = sd.region_align_down(last_word); - - if (beg_region_addr == end_region_addr) { - // One destination region. - _destination_count = 1; - if (end_region_addr == destination) { - // The destination falls on a region boundary, thus the first word of the - // partial object will be the first word copied to the destination region. - _dest_region_addr = end_region_addr; - _first_src_addr = sd.region_to_addr(src_region_idx); - } + + PSParallelCompact::RegionData* split_region_ptr = sd.region(split_region_idx); + assert(preceding_live_words < split_region_ptr->data_size(), "inv"); + + HeapWord* preceding_destination = split_region_ptr->destination(); + assert(preceding_destination != nullptr, "inv"); + + // How many regions does the preceding part occupy + uint preceding_destination_count; + if (preceding_live_words == 0) { + preceding_destination_count = 0; } else { - // Two destination regions. When copied, the partial object will cross a - // destination region boundary, so a word somewhere within the partial - // object will be the first word copied to the second destination region. - _destination_count = 2; - _dest_region_addr = end_region_addr; - const size_t ofs = pointer_delta(end_region_addr, destination); - assert(ofs < _partial_obj_size, "sanity"); - _first_src_addr = sd.region_to_addr(src_region_idx) + ofs; + // -1 so that the ending address doesn't fall on the region-boundary + if (sd.region_align_down(preceding_destination) == + sd.region_align_down(preceding_destination + preceding_live_words - 1)) { + preceding_destination_count = 1; + } else { + preceding_destination_count = 2; + } } + + _split_region_idx = split_region_idx; + _split_point = split_point; + _preceding_live_words = preceding_live_words; + _preceding_destination = preceding_destination; + _preceding_destination_count = preceding_destination_count; } void SplitInfo::clear() { - _src_region_idx = 0; - _partial_obj_size = 0; - _destination = nullptr; - _destination_count = 0; - _dest_region_addr = nullptr; - _first_src_addr = nullptr; + _split_region_idx = 0; + _split_point = nullptr; + _preceding_live_words = 0; + _preceding_destination = nullptr; + _preceding_destination_count = 0; assert(!is_valid(), "sanity"); } #ifdef ASSERT void SplitInfo::verify_clear() { - assert(_src_region_idx == 0, "not clear"); - assert(_partial_obj_size == 0, "not clear"); - assert(_destination == nullptr, "not clear"); - assert(_destination_count == 0, "not clear"); - assert(_dest_region_addr == nullptr, "not clear"); - assert(_first_src_addr == nullptr, "not clear"); + assert(_split_region_idx == 0, "not clear"); + assert(_split_point == nullptr, "not clear"); + assert(_preceding_live_words == 0, "not clear"); + assert(_preceding_destination == nullptr, "not clear"); + assert(_preceding_destination_count == 0, "not clear"); } #endif // #ifdef ASSERT @@ -297,110 +308,105 @@ ParallelCompactData::summarize_dense_prefix(HeapWord* beg, HeapWord* end) } } -// Find the point at which a space can be split and, if necessary, record the -// split point. -// -// If the current src region (which overflowed the destination space) doesn't -// have a partial object, the split point is at the beginning of the current src -// region (an "easy" split, no extra bookkeeping required). -// -// If the current src region has a partial object, the split point is in the -// region where that partial object starts (call it the split_region). If -// split_region has a partial object, then the split point is just after that -// partial object (a "hard" split where we have to record the split data and -// zero the partial_obj_size field). With a "hard" split, we know that the -// partial_obj ends within split_region because the partial object that caused -// the overflow starts in split_region. If split_region doesn't have a partial -// obj, then the split is at the beginning of split_region (another "easy" -// split). -HeapWord* -ParallelCompactData::summarize_split_space(size_t src_region, - SplitInfo& split_info, - HeapWord* destination, - HeapWord* target_end, - HeapWord** target_next) -{ +// The total live words on src_region would overflow the target space, so find +// the overflowing object and record the split point. The invariant is that an +// obj should not cross space boundary. +HeapWord* ParallelCompactData::summarize_split_space(size_t src_region, + SplitInfo& split_info, + HeapWord* const destination, + HeapWord* const target_end, + HeapWord** target_next) { assert(destination <= target_end, "sanity"); assert(destination + _region_data[src_region].data_size() > target_end, "region should not fit into target space"); assert(is_region_aligned(target_end), "sanity"); - size_t split_region = src_region; - HeapWord* split_destination = destination; size_t partial_obj_size = _region_data[src_region].partial_obj_size(); if (destination + partial_obj_size > target_end) { - // The split point is just after the partial object (if any) in the - // src_region that contains the start of the object that overflowed the - // destination space. + assert(partial_obj_size > 0, "inv"); + // The overflowing obj is from a previous region. + // + // source-regions: // - // Find the start of the "overflow" object and set split_region to the - // region containing it. - HeapWord* const overflow_obj = _region_data[src_region].partial_obj_addr(); - split_region = addr_to_region_idx(overflow_obj); - - // Clear the source_region field of all destination regions whose first word - // came from data after the split point (a non-null source_region field - // implies a region must be filled). + // *************** + // | A|AA | + // *************** + // ^ + // | split-point // - // An alternative to the simple loop below: clear during post_compact(), - // which uses memcpy instead of individual stores, and is easy to - // parallelize. (The downside is that it clears the entire RegionData - // object as opposed to just one field.) + // dest-region: // - // post_compact() would have to clear the summary data up to the highest - // address that was written during the summary phase, which would be + // ******** + // |~~~~A | + // ******** + // ^^ + // || target-space-end + // | + // | destination // - // max(top, max(new_top, clear_top)) + // AAA would overflow target-space. // - // where clear_top is a new field in SpaceInfo. Would have to set clear_top - // to target_end. - const RegionData* const sr = region(split_region); - const size_t beg_idx = - addr_to_region_idx(region_align_up(sr->destination() + - sr->partial_obj_size())); - const size_t end_idx = addr_to_region_idx(target_end); + HeapWord* overflowing_obj = _region_data[src_region].partial_obj_addr(); + size_t split_region = addr_to_region_idx(overflowing_obj); - log_develop_trace(gc, compaction)("split: clearing source_region field in [" SIZE_FORMAT ", " SIZE_FORMAT ")", beg_idx, end_idx); - for (size_t idx = beg_idx; idx < end_idx; ++idx) { - _region_data[idx].set_source_region(0); + // The number of live words before the overflowing object on this split region + size_t preceding_live_words; + if (is_region_aligned(overflowing_obj)) { + preceding_live_words = 0; + } else { + // Words accounted by the overflowing object on the split region + size_t overflowing_size = pointer_delta(region_align_up(overflowing_obj), overflowing_obj); + preceding_live_words = region(split_region)->data_size() - overflowing_size; } - // Set split_destination and partial_obj_size to reflect the split region. - split_destination = sr->destination(); - partial_obj_size = sr->partial_obj_size(); - } + split_info.record(split_region, overflowing_obj, preceding_live_words); + + HeapWord* src_region_start = region_to_addr(src_region); + HeapWord* new_top = destination - pointer_delta(src_region_start, overflowing_obj); + + // If the overflowing obj was relocated to its original destination, + // those destination regions would have their source_region set. Now that + // this overflowing obj is relocated somewhere else, reset the + // source_region. + { + size_t range_start = addr_to_region_idx(region_align_up(new_top)); + size_t range_end = addr_to_region_idx(region_align_up(destination)); + for (size_t i = range_start; i < range_end; ++i) { + region(i)->set_source_region(0); + } + } - // The split is recorded only if a partial object extends onto the region. - if (partial_obj_size != 0) { - _region_data[split_region].set_partial_obj_size(0); - split_info.record(split_region, partial_obj_size, split_destination); + // Update new top of target space + *target_next = new_top; + + return overflowing_obj; } - // Setup the continuation addresses. - *target_next = split_destination + partial_obj_size; - HeapWord* const source_next = region_to_addr(split_region) + partial_obj_size; + // Obj-iteration to locate the overflowing obj + HeapWord* region_start = region_to_addr(src_region); + HeapWord* region_end = region_start + RegionSize; + HeapWord* cur_addr = region_start + partial_obj_size; + size_t live_words = partial_obj_size; - if (log_develop_is_enabled(Trace, gc, compaction)) { - const char * split_type = partial_obj_size == 0 ? "easy" : "hard"; - log_develop_trace(gc, compaction)("%s split: src=" PTR_FORMAT " src_c=" SIZE_FORMAT " pos=" SIZE_FORMAT, - split_type, p2i(source_next), split_region, partial_obj_size); - log_develop_trace(gc, compaction)("%s split: dst=" PTR_FORMAT " dst_c=" SIZE_FORMAT " tn=" PTR_FORMAT, - split_type, p2i(split_destination), - addr_to_region_idx(split_destination), - p2i(*target_next)); + while (true) { + assert(cur_addr < region_end, "inv"); + cur_addr = PSParallelCompact::mark_bitmap()->find_obj_beg(cur_addr, region_end); + // There must be an overflowing obj in this region + assert(cur_addr < region_end, "inv"); - if (partial_obj_size != 0) { - HeapWord* const po_beg = split_info.destination(); - HeapWord* const po_end = po_beg + split_info.partial_obj_size(); - log_develop_trace(gc, compaction)("%s split: po_beg=" PTR_FORMAT " " SIZE_FORMAT " po_end=" PTR_FORMAT " " SIZE_FORMAT, - split_type, - p2i(po_beg), addr_to_region_idx(po_beg), - p2i(po_end), addr_to_region_idx(po_end)); + oop obj = cast_to_oop(cur_addr); + size_t obj_size = obj->size(); + if (destination + live_words + obj_size > target_end) { + // Found the overflowing obj + split_info.record(src_region, cur_addr, live_words); + *target_next = destination + live_words; + return cur_addr; } - } - return source_next; + live_words += obj_size; + cur_addr += obj_size; + } } size_t ParallelCompactData::live_words_in_space(const MutableSpace* space, @@ -452,70 +458,57 @@ bool ParallelCompactData::summarize(SplitInfo& split_info, const size_t end_region = addr_to_region_idx(region_align_up(source_end)); HeapWord *dest_addr = target_beg; - while (cur_region < end_region) { - // The destination must be set even if the region has no data. + for (/* empty */; cur_region < end_region; cur_region++) { + size_t words = _region_data[cur_region].data_size(); + + // Skip empty ones + if (words == 0) { + continue; + } + + if (split_info.is_split(cur_region)) { + assert(words > split_info.preceding_live_words(), "inv"); + words -= split_info.preceding_live_words(); + } + _region_data[cur_region].set_destination(dest_addr); - size_t words = _region_data[cur_region].data_size(); - if (words > 0) { - // If cur_region does not fit entirely into the target space, find a point - // at which the source space can be 'split' so that part is copied to the - // target space and the rest is copied elsewhere. - if (dest_addr + words > target_end) { - assert(source_next != nullptr, "source_next is null when splitting"); - *source_next = summarize_split_space(cur_region, split_info, dest_addr, - target_end, target_next); - return false; - } + // If cur_region does not fit entirely into the target space, find a point + // at which the source space can be 'split' so that part is copied to the + // target space and the rest is copied elsewhere. + if (dest_addr + words > target_end) { + assert(source_next != nullptr, "source_next is null when splitting"); + *source_next = summarize_split_space(cur_region, split_info, dest_addr, + target_end, target_next); + return false; + } - // Compute the destination_count for cur_region, and if necessary, update - // source_region for a destination region. The source_region field is - // updated if cur_region is the first (left-most) region to be copied to a - // destination region. - // - // The destination_count calculation is a bit subtle. A region that has - // data that compacts into itself does not count itself as a destination. - // This maintains the invariant that a zero count means the region is - // available and can be claimed and then filled. - uint destination_count = 0; - if (split_info.is_split(cur_region)) { - // The current region has been split: the partial object will be copied - // to one destination space and the remaining data will be copied to - // another destination space. Adjust the initial destination_count and, - // if necessary, set the source_region field if the partial object will - // cross a destination region boundary. - destination_count = split_info.destination_count(); - if (destination_count == 2) { - size_t dest_idx = addr_to_region_idx(split_info.dest_region_addr()); - _region_data[dest_idx].set_source_region(cur_region); - } - } + uint destination_count = split_info.is_split(cur_region) + ? split_info.preceding_destination_count() + : 0; - HeapWord* const last_addr = dest_addr + words - 1; - const size_t dest_region_1 = addr_to_region_idx(dest_addr); - const size_t dest_region_2 = addr_to_region_idx(last_addr); - - // Initially assume that the destination regions will be the same and - // adjust the value below if necessary. Under this assumption, if - // cur_region == dest_region_2, then cur_region will be compacted - // completely into itself. - destination_count += cur_region == dest_region_2 ? 0 : 1; - if (dest_region_1 != dest_region_2) { - // Destination regions differ; adjust destination_count. - destination_count += 1; - // Data from cur_region will be copied to the start of dest_region_2. - _region_data[dest_region_2].set_source_region(cur_region); - } else if (is_region_aligned(dest_addr)) { - // Data from cur_region will be copied to the start of the destination - // region. - _region_data[dest_region_1].set_source_region(cur_region); - } + HeapWord* const last_addr = dest_addr + words - 1; + const size_t dest_region_1 = addr_to_region_idx(dest_addr); + const size_t dest_region_2 = addr_to_region_idx(last_addr); - _region_data[cur_region].set_destination_count(destination_count); - dest_addr += words; + // Initially assume that the destination regions will be the same and + // adjust the value below if necessary. Under this assumption, if + // cur_region == dest_region_2, then cur_region will be compacted + // completely into itself. + destination_count += cur_region == dest_region_2 ? 0 : 1; + if (dest_region_1 != dest_region_2) { + // Destination regions differ; adjust destination_count. + destination_count += 1; + // Data from cur_region will be copied to the start of dest_region_2. + _region_data[dest_region_2].set_source_region(cur_region); + } else if (is_region_aligned(dest_addr)) { + // Data from cur_region will be copied to the start of the destination + // region. + _region_data[dest_region_1].set_source_region(cur_region); } - ++cur_region; + _region_data[cur_region].set_destination_count(destination_count); + dest_addr += words; } *target_next = dest_addr; @@ -523,12 +516,12 @@ bool ParallelCompactData::summarize(SplitInfo& split_info, } #ifdef ASSERT -void ParallelCompactData::verify_clear() -{ - const size_t* const beg = (const size_t*) _region_vspace->committed_low_addr(); - const size_t* const end = (const size_t*) _region_vspace->committed_high_addr(); - for (const size_t* p = beg; p < end; ++p) { - assert(*p == 0, "not zero"); +void ParallelCompactData::verify_clear() { + for (uint cur_idx = 0; cur_idx < region_count(); ++cur_idx) { + if (!region(cur_idx)->is_clear()) { + log_warning(gc)("Uncleared Region: %u", cur_idx); + region(cur_idx)->verify_clear(); + } } } #endif // #ifdef ASSERT @@ -695,6 +688,13 @@ void PSParallelCompact::post_compact() } } +#ifdef ASSERT + { + mark_bitmap()->verify_clear(); + summary_data().verify_clear(); + } +#endif + ParCompactionManager::flush_all_string_dedup_requests(); MutableSpace* const eden_space = _space_info[eden_space_id].space(); @@ -878,10 +878,10 @@ void PSParallelCompact::summary_phase() bool maximum_compaction = check_maximum_compaction(total_live_words, old_space, full_region_prefix_end); - HeapWord* dense_prefix_end = - maximum_compaction ? full_region_prefix_end - : compute_dense_prefix_for_old_space(old_space, - full_region_prefix_end); + HeapWord* dense_prefix_end = maximum_compaction + ? full_region_prefix_end + : compute_dense_prefix_for_old_space(old_space, + full_region_prefix_end); SpaceId id = old_space_id; _space_info[id].set_dense_prefix(dense_prefix_end); @@ -889,6 +889,8 @@ void PSParallelCompact::summary_phase() fill_dense_prefix_end(id); _summary_data.summarize_dense_prefix(old_space->bottom(), dense_prefix_end); } + + // Compacting objs inn [dense_prefix_end, old_space->top()) _summary_data.summarize(_space_info[id].split_info(), dense_prefix_end, old_space->top(), nullptr, dense_prefix_end, old_space->end(), @@ -1548,6 +1550,30 @@ void PSParallelCompact::forward_to_new_addr() { WorkerTask("PSForward task"), _num_workers(num_workers) {} + static void forward_objs_in_range(ParCompactionManager* cm, + HeapWord* start, + HeapWord* end, + HeapWord* destination) { + HeapWord* cur_addr = start; + HeapWord* new_addr = destination; + + while (cur_addr < end) { + cur_addr = mark_bitmap()->find_obj_beg(cur_addr, end); + if (cur_addr >= end) { + return; + } + assert(mark_bitmap()->is_marked(cur_addr), "inv"); + oop obj = cast_to_oop(cur_addr); + if (new_addr != cur_addr) { + cm->preserved_marks()->push_if_necessary(obj, obj->mark()); + FullGCForwarding::forward_to(obj, cast_to_oop(new_addr)); + } + size_t obj_size = obj->size(); + new_addr += obj_size; + cur_addr += obj_size; + } + } + void work(uint worker_id) override { ParCompactionManager* cm = ParCompactionManager::gc_thread_compaction_manager(worker_id); for (uint id = old_space_id; id < last_space_id; ++id) { @@ -1559,6 +1585,8 @@ void PSParallelCompact::forward_to_new_addr() { continue; } + const SplitInfo& split_info = _space_info[SpaceId(id)].split_info(); + size_t dense_prefix_region = _summary_data.addr_to_region_idx(dense_prefix_addr); size_t top_region = _summary_data.addr_to_region_idx(_summary_data.region_align_up(top)); size_t start_region; @@ -1578,24 +1606,19 @@ void PSParallelCompact::forward_to_new_addr() { HeapWord* region_start = _summary_data.region_to_addr(cur_region); HeapWord* region_end = region_start + ParallelCompactData::RegionSize; - HeapWord* cur_addr = region_start + live_words; - - HeapWord* destination = region_ptr->destination(); - while (cur_addr < region_end) { - cur_addr = mark_bitmap()->find_obj_beg(cur_addr, region_end); - if (cur_addr >= region_end) { - break; - } - assert(mark_bitmap()->is_marked(cur_addr), "inv"); - HeapWord* new_addr = destination + live_words; - oop obj = cast_to_oop(cur_addr); - if (new_addr != cur_addr) { - cm->preserved_marks()->push_if_necessary(obj, obj->mark()); - FullGCForwarding::forward_to(obj, cast_to_oop(new_addr)); - } - size_t obj_size = obj->size(); - live_words += obj_size; - cur_addr += obj_size; + + if (split_info.is_split(cur_region)) { + // Part 1: will be relocated to space-1 + HeapWord* preceding_destination = split_info.preceding_destination(); + HeapWord* split_point = split_info.split_point(); + forward_objs_in_range(cm, region_start + live_words, split_point, preceding_destination + live_words); + + // Part 2: will be relocated to space-2 + HeapWord* destination = region_ptr->destination(); + forward_objs_in_range(cm, split_point, region_end, destination); + } else { + HeapWord* destination = region_ptr->destination(); + forward_objs_in_range(cm, region_start + live_words, region_end, destination + live_words); } } } @@ -1627,13 +1650,16 @@ void PSParallelCompact::verify_forward() { break; } assert(mark_bitmap()->is_marked(cur_addr), "inv"); + assert(bump_ptr <= _space_info[bump_ptr_space].new_top(), "inv"); // Move to the space containing cur_addr if (bump_ptr == _space_info[bump_ptr_space].new_top()) { bump_ptr = space(space_id(cur_addr))->bottom(); bump_ptr_space = space_id(bump_ptr); } oop obj = cast_to_oop(cur_addr); - if (cur_addr != bump_ptr) { + if (cur_addr == bump_ptr) { + assert(!FullGCForwarding::is_forwarded(obj), "inv"); + } else { assert(FullGCForwarding::forwardee(obj) == cast_to_oop(bump_ptr), "inv"); } bump_ptr += obj->size(); @@ -1939,15 +1965,10 @@ PSParallelCompact::SpaceId PSParallelCompact::space_id(HeapWord* addr) { } // Skip over count live words starting from beg, and return the address of the -// next live word. Unless marked, the word corresponding to beg is assumed to -// be dead. Callers must either ensure beg does not correspond to the middle of -// an object, or account for those live words in some other way. Callers must -// also ensure that there are enough live words in the range [beg, end) to skip. -HeapWord* -PSParallelCompact::skip_live_words(HeapWord* beg, HeapWord* end, size_t count) +// next live word. Callers must also ensure that there are enough live words in +// the range [beg, end) to skip. +HeapWord* PSParallelCompact::skip_live_words(HeapWord* beg, HeapWord* end, size_t count) { - assert(count > 0, "sanity"); - ParMarkBitMap* m = mark_bitmap(); HeapWord* cur_addr = beg; while (true) { @@ -1963,69 +1984,94 @@ PSParallelCompact::skip_live_words(HeapWord* beg, HeapWord* end, size_t count) } } +// On filling a destination region (dest-region), we need to know the location +// of the word that will be at the start of the dest-region after compaction. +// A dest-region can have one or more source regions, but only the first +// source-region contains this location. This location is retrieved by calling +// `first_src_addr` on a dest-region. +// Conversely, a source-region has a dest-region which holds the destination of +// the first live word on this source-region, based on which the destination +// for the rest of live words can be derived. +// +// Note: +// There is some complication due to space-boundary-fragmentation (an obj can't +// cross space-boundary) -- a source-region may be split and behave like two +// distinct regions with their own dest-region, as depicted below. +// +// source-region: region-n +// +// ********************** +// | A|A~~~~B|B | +// ********************** +// n-1 n n+1 +// +// AA, BB denote two live objs. ~~~~ denotes unknown number of live objs. +// +// Assuming the dest-region for region-n is the final region before +// old-space-end and its first-live-word is the middle of AA, the heap content +// will look like the following after compaction: +// +// ************** ************* +// A|A~~~~ | |BB | +// ************** ************* +// ^ ^ +// | old-space-end | eden-space-start +// +// Therefore, in this example, region-n will have two dest-regions, one for +// the final region in old-space and the other for the first region in +// eden-space. +// To handle this special case, we introduce the concept of split-region, whose +// contents are relocated to two spaces. `SplitInfo` captures all necessary +// info about the split, the first part, spliting-point, and the second part. HeapWord* PSParallelCompact::first_src_addr(HeapWord* const dest_addr, SpaceId src_space_id, size_t src_region_idx) { - assert(summary_data().is_region_aligned(dest_addr), "not aligned"); - - const SplitInfo& split_info = _space_info[src_space_id].split_info(); - if (split_info.dest_region_addr() == dest_addr) { - // The partial object ending at the split point contains the first word to - // be copied to dest_addr. - return split_info.first_src_addr(); - } - - const ParallelCompactData& sd = summary_data(); - ParMarkBitMap* const bitmap = mark_bitmap(); const size_t RegionSize = ParallelCompactData::RegionSize; + const ParallelCompactData& sd = summary_data(); + assert(sd.is_region_aligned(dest_addr), "precondition"); - assert(sd.is_region_aligned(dest_addr), "not aligned"); const RegionData* const src_region_ptr = sd.region(src_region_idx); + assert(src_region_ptr->data_size() > 0, "src region cannot be empty"); + const size_t partial_obj_size = src_region_ptr->partial_obj_size(); HeapWord* const src_region_destination = src_region_ptr->destination(); - assert(dest_addr >= src_region_destination, "wrong src region"); - assert(src_region_ptr->data_size() > 0, "src region cannot be empty"); - - HeapWord* const src_region_beg = sd.region_to_addr(src_region_idx); - HeapWord* const src_region_end = src_region_beg + RegionSize; + HeapWord* const region_start = sd.region_to_addr(src_region_idx); + HeapWord* const region_end = sd.region_to_addr(src_region_idx) + RegionSize; - HeapWord* addr = src_region_beg; - if (dest_addr == src_region_destination) { - // Return the first live word in the source region. - if (partial_obj_size == 0) { - addr = bitmap->find_obj_beg(addr, src_region_end); - assert(addr < src_region_end, "no objects start in src region"); + // Identify the actual destination for the first live words on this region, + // taking split-region into account. + HeapWord* region_start_destination; + const SplitInfo& split_info = _space_info[src_space_id].split_info(); + if (split_info.is_split(src_region_idx)) { + // The second part of this split region; use the recorded split point. + if (dest_addr == src_region_destination) { + return split_info.split_point(); } - return addr; + region_start_destination = split_info.preceding_destination(); + } else { + region_start_destination = src_region_destination; } - // Must skip some live data. - size_t words_to_skip = dest_addr - src_region_destination; - assert(src_region_ptr->data_size() > words_to_skip, "wrong src region"); + // Calculate the offset to be skipped + size_t words_to_skip = pointer_delta(dest_addr, region_start_destination); - if (partial_obj_size >= words_to_skip) { - // All the live words to skip are part of the partial object. - addr += words_to_skip; - if (partial_obj_size == words_to_skip) { - // Find the first live word past the partial object. - addr = bitmap->find_obj_beg(addr, src_region_end); - assert(addr < src_region_end, "wrong src region"); - } - return addr; + HeapWord* result; + if (partial_obj_size > words_to_skip) { + result = region_start + words_to_skip; + } else { + words_to_skip -= partial_obj_size; + result = skip_live_words(region_start + partial_obj_size, region_end, words_to_skip); } - // Skip over the partial object (if any). - if (partial_obj_size != 0) { - words_to_skip -= partial_obj_size; - addr += partial_obj_size; + if (split_info.is_split(src_region_idx)) { + assert(result < split_info.split_point(), "postcondition"); + } else { + assert(result < region_end, "postcondition"); } - // Skip over live words due to objects that start in the region. - addr = skip_live_words(addr, src_region_end, words_to_skip); - assert(addr < src_region_end, "wrong src region"); - return addr; + return result; } void PSParallelCompact::decrement_destination_counts(ParCompactionManager* cm, @@ -2076,10 +2122,7 @@ size_t PSParallelCompact::next_src_region(MoveAndUpdateClosure& closure, HeapWord*& src_space_top, HeapWord* end_addr) { - typedef ParallelCompactData::RegionData RegionData; - ParallelCompactData& sd = PSParallelCompact::summary_data(); - const size_t region_size = ParallelCompactData::RegionSize; size_t src_region_idx = 0; @@ -2087,8 +2130,8 @@ size_t PSParallelCompact::next_src_region(MoveAndUpdateClosure& closure, HeapWord* const src_aligned_up = sd.region_align_up(end_addr); RegionData* src_region_ptr = sd.addr_to_region_ptr(src_aligned_up); HeapWord* const top_aligned_up = sd.region_align_up(src_space_top); - const RegionData* const top_region_ptr = - sd.addr_to_region_ptr(top_aligned_up); + const RegionData* const top_region_ptr = sd.addr_to_region_ptr(top_aligned_up); + while (src_region_ptr < top_region_ptr && src_region_ptr->data_size() == 0) { ++src_region_ptr; } @@ -2105,59 +2148,50 @@ size_t PSParallelCompact::next_src_region(MoveAndUpdateClosure& closure, } // Switch to a new source space and find the first non-empty region. - unsigned int space_id = src_space_id + 1; + uint space_id = src_space_id + 1; assert(space_id < last_space_id, "not enough spaces"); - HeapWord* const destination = closure.destination(); - - do { - MutableSpace* space = _space_info[space_id].space(); - HeapWord* const bottom = space->bottom(); - const RegionData* const bottom_cp = sd.addr_to_region_ptr(bottom); - - // Iterate over the spaces that do not compact into themselves. - if (bottom_cp->destination() != bottom) { - HeapWord* const top_aligned_up = sd.region_align_up(space->top()); - const RegionData* const top_cp = sd.addr_to_region_ptr(top_aligned_up); - - for (const RegionData* src_cp = bottom_cp; src_cp < top_cp; ++src_cp) { - if (src_cp->live_obj_size() > 0) { - // Found it. - assert(src_cp->destination() == destination, - "first live obj in the space must match the destination"); - assert(src_cp->partial_obj_size() == 0, - "a space cannot begin with a partial obj"); - - src_space_id = SpaceId(space_id); - src_space_top = space->top(); - const size_t src_region_idx = sd.region(src_cp); - closure.set_source(sd.region_to_addr(src_region_idx)); - return src_region_idx; - } else { - assert(src_cp->data_size() == 0, "sanity"); - } + for (/* empty */; space_id < last_space_id; ++space_id) { + HeapWord* bottom = _space_info[space_id].space()->bottom(); + HeapWord* top = _space_info[space_id].space()->top(); + // Skip empty space + if (bottom == top) { + continue; + } + + // Identify the first region that contains live words in this space + size_t cur_region = sd.addr_to_region_idx(bottom); + size_t end_region = sd.addr_to_region_idx(sd.region_align_up(top)); + + for (/* empty */ ; cur_region < end_region; ++cur_region) { + RegionData* cur = sd.region(cur_region); + if (cur->live_obj_size() > 0) { + HeapWord* region_start_addr = sd.region_to_addr(cur_region); + HeapWord* region_end_addr = region_start_addr + ParallelCompactData::RegionSize; + HeapWord* first_live_word = mark_bitmap()->find_obj_beg(region_start_addr, region_end_addr); + assert(first_live_word < region_end_addr, "inv"); + + src_space_id = SpaceId(space_id); + src_space_top = top; + closure.set_source(first_live_word); + return cur_region; } } - } while (++space_id < last_space_id); + } - assert(false, "no source region was found"); - return 0; + ShouldNotReachHere(); } HeapWord* PSParallelCompact::partial_obj_end(HeapWord* region_start_addr) { ParallelCompactData& sd = summary_data(); assert(sd.is_region_aligned(region_start_addr), "precondition"); - // Use per-region partial_obj_size to locate the end of the obj, that extends to region_start_addr. - SplitInfo& split_info = _space_info[space_id(region_start_addr)].split_info(); + // Use per-region partial_obj_size to locate the end of the obj, that extends + // to region_start_addr. size_t start_region_idx = sd.addr_to_region_idx(region_start_addr); size_t end_region_idx = sd.region_count(); size_t accumulated_size = 0; for (size_t region_idx = start_region_idx; region_idx < end_region_idx; ++region_idx) { - if (split_info.is_split(region_idx)) { - accumulated_size += split_info.partial_obj_size(); - break; - } size_t cur_partial_obj_size = sd.region(region_idx)->partial_obj_size(); accumulated_size += cur_partial_obj_size; if (cur_partial_obj_size != ParallelCompactData::RegionSize) { @@ -2167,6 +2201,8 @@ HeapWord* PSParallelCompact::partial_obj_end(HeapWord* region_start_addr) { return region_start_addr + accumulated_size; } +// Use region_idx as the destination region, and evacuate all live objs on its +// source regions to this destination region. void PSParallelCompact::fill_region(ParCompactionManager* cm, MoveAndUpdateClosure& closure, size_t region_idx) { ParMarkBitMap* const bitmap = mark_bitmap(); @@ -2187,20 +2223,43 @@ void PSParallelCompact::fill_region(ParCompactionManager* cm, MoveAndUpdateClosu src_region_idx += 1; } + // source-region: + // + // ********** + // | ~~~ | + // ********** + // ^ + // |-- closure.source() / first_src_addr + // + // + // ~~~ : live words + // + // destination-region: + // + // ********** + // | | + // ********** + // ^ + // |-- region-start if (bitmap->is_unmarked(closure.source())) { - // The first source word is in the middle of an object; copy the remainder - // of the object or as much as will fit. The fact that pointer updates were - // deferred will be noted when the object header is processed. + // An object overflows the previous destination region, so this + // destination region should copy the remainder of the object or as much as + // will fit. HeapWord* const old_src_addr = closure.source(); { HeapWord* region_start = sd.region_align_down(closure.source()); HeapWord* obj_start = bitmap->find_obj_beg_reverse(region_start, closure.source()); HeapWord* obj_end; - if (bitmap->is_marked(obj_start)) { + if (obj_start != closure.source()) { + assert(bitmap->is_marked(obj_start), "inv"); + // Found the actual obj-start, try to find the obj-end using either + // size() if this obj is completely contained in the current region. HeapWord* next_region_start = region_start + ParallelCompactData::RegionSize; HeapWord* partial_obj_start = (next_region_start >= src_space_top) ? nullptr : sd.addr_to_region_ptr(next_region_start)->partial_obj_addr(); + // This obj extends to next region iff partial_obj_addr of the *next* + // region is the same as obj-start. if (partial_obj_start == obj_start) { // This obj extends to next region. obj_end = partial_obj_end(next_region_start); @@ -2217,39 +2276,41 @@ void PSParallelCompact::fill_region(ParCompactionManager* cm, MoveAndUpdateClosu } if (closure.is_full()) { - decrement_destination_counts(cm, src_space_id, src_region_idx, - closure.source()); + decrement_destination_counts(cm, src_space_id, src_region_idx, closure.source()); closure.complete_region(dest_addr, region_ptr); return; } + // Finished copying without using up the current destination-region HeapWord* const end_addr = sd.region_align_down(closure.source()); if (sd.region_align_down(old_src_addr) != end_addr) { + assert(sd.region_align_up(old_src_addr) == end_addr, "only one region"); // The partial object was copied from more than one source region. decrement_destination_counts(cm, src_space_id, src_region_idx, end_addr); // Move to the next source region, possibly switching spaces as well. All // args except end_addr may be modified. - src_region_idx = next_src_region(closure, src_space_id, src_space_top, - end_addr); + src_region_idx = next_src_region(closure, src_space_id, src_space_top, end_addr); } } + // Handle the rest obj-by-obj, where we know obj-start. do { HeapWord* cur_addr = closure.source(); HeapWord* const end_addr = MIN2(sd.region_align_up(cur_addr + 1), src_space_top); - HeapWord* partial_obj_start = (end_addr == src_space_top) + // To handle the case where the final obj in source region extends to next region. + HeapWord* final_obj_start = (end_addr == src_space_top) ? nullptr : sd.addr_to_region_ptr(end_addr)->partial_obj_addr(); - // apply closure on objs inside [cur_addr, end_addr) + // Apply closure on objs inside [cur_addr, end_addr) do { cur_addr = bitmap->find_obj_beg(cur_addr, end_addr); if (cur_addr == end_addr) { break; } size_t obj_size; - if (partial_obj_start == cur_addr) { + if (final_obj_start == cur_addr) { obj_size = pointer_delta(partial_obj_end(end_addr), cur_addr); } else { // This obj doesn't extend into next region; size() is safe to use. @@ -2260,8 +2321,7 @@ void PSParallelCompact::fill_region(ParCompactionManager* cm, MoveAndUpdateClosu } while (cur_addr < end_addr && !closure.is_full()); if (closure.is_full()) { - decrement_destination_counts(cm, src_space_id, src_region_idx, - closure.source()); + decrement_destination_counts(cm, src_space_id, src_region_idx, closure.source()); closure.complete_region(dest_addr, region_ptr); return; } @@ -2270,8 +2330,7 @@ void PSParallelCompact::fill_region(ParCompactionManager* cm, MoveAndUpdateClosu // Move to the next source region, possibly switching spaces as well. All // args except end_addr may be modified. - src_region_idx = next_src_region(closure, src_space_id, src_space_top, - end_addr); + src_region_idx = next_src_region(closure, src_space_id, src_space_top, end_addr); } while (true); } diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.hpp b/src/hotspot/share/gc/parallel/psParallelCompact.hpp index 3f487ec3ef483..878a09e283b18 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.hpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.hpp @@ -116,51 +116,45 @@ class SplitInfo // Return true if this split info is valid (i.e., if a split has been // recorded). The very first region cannot have a partial object and thus is // never split, so 0 is the 'invalid' value. - bool is_valid() const { return _src_region_idx > 0; } + bool is_valid() const { return _split_region_idx > 0; } // Return true if this split holds data for the specified source region. - inline bool is_split(size_t source_region) const; + inline bool is_split(size_t region_idx) const; - // The index of the split region, the size of the partial object on that - // region and the destination of the partial object. - size_t partial_obj_size() const { return _partial_obj_size; } - HeapWord* destination() const { return _destination; } + // Obj at the split point doesn't fit the previous space and will be relocated to the next space. + HeapWord* split_point() const { return _split_point; } - // The destination count of the partial object referenced by this split - // (either 1 or 2). This must be added to the destination count of the - // remainder of the source region. - unsigned int destination_count() const { return _destination_count; } + // Number of live words before the split point on this region. + size_t preceding_live_words() const { return _preceding_live_words; } - // If a word within the partial object will be written to the first word of a - // destination region, this is the address of the destination region; - // otherwise this is null. - HeapWord* dest_region_addr() const { return _dest_region_addr; } + // A split region has two "destinations", living in two spaces. This method + // returns the first one -- destination for the first live word on + // this split region. + HeapWord* preceding_destination() const { + assert(_preceding_destination != nullptr, "inv"); + return _preceding_destination; + } - // If a word within the partial object will be written to the first word of a - // destination region, this is the address of that word within the partial - // object; otherwise this is null. - HeapWord* first_src_addr() const { return _first_src_addr; } + // Number of regions the preceding live words are relocated into. + uint preceding_destination_count() const { return _preceding_destination_count; } - // Record the data necessary to split the region src_region_idx. - void record(size_t src_region_idx, size_t partial_obj_size, - HeapWord* destination); + void record(size_t split_region_idx, HeapWord* split_point, size_t preceding_live_words); void clear(); DEBUG_ONLY(void verify_clear();) private: - size_t _src_region_idx; - size_t _partial_obj_size; - HeapWord* _destination; - unsigned int _destination_count; - HeapWord* _dest_region_addr; - HeapWord* _first_src_addr; + size_t _split_region_idx; + HeapWord* _split_point; + size_t _preceding_live_words; + HeapWord* _preceding_destination; + uint _preceding_destination_count; }; inline bool SplitInfo::is_split(size_t region_idx) const { - return _src_region_idx == region_idx && is_valid(); + return _split_region_idx == region_idx && is_valid(); } class SpaceInfo @@ -215,10 +209,18 @@ class ParallelCompactData class RegionData { public: - // Destination address of the region. + // Destination for the first live word in this region. + // Therefore, the new addr for every live obj on this region can be calculated as: + // + // new_addr := _destination + live_words_offset(old_addr); + // + // where, live_words_offset is the number of live words accumulated from + // region-start to old_addr. HeapWord* destination() const { return _destination; } - // The first region containing data destined for this region. + // A destination region can have multiple source regions; only the first + // one is recorded. Since all live objs are slided down, subsequent source + // regions can be found via plain heap-region iteration. size_t source_region() const { return _source_region; } // Reuse _source_region to store the corresponding shadow region index @@ -313,9 +315,12 @@ class ParallelCompactData // Return to the normal path here inline void shadow_to_normal(); - int shadow_state() { return _shadow_state; } + bool is_clear(); + + void verify_clear() NOT_DEBUG_RETURN; + private: // The type used to represent object sizes within a region. typedef uint region_sz_t; @@ -873,7 +878,10 @@ class MoveAndUpdateClosure: public StackObj { size_t words_remaining() const { return _words_remaining; } bool is_full() const { return _words_remaining == 0; } HeapWord* source() const { return _source; } - void set_source(HeapWord* addr) { _source = addr; } + void set_source(HeapWord* addr) { + assert(addr != nullptr, "precondition"); + _source = addr; + } // If the object will fit (size <= words_remaining()), copy it to the current // destination, update the interior oops and the start array. @@ -902,9 +910,8 @@ inline size_t MoveAndUpdateClosure::calculate_words_remaining(size_t region) { HeapWord* dest_addr = PSParallelCompact::summary_data().region_to_addr(region); PSParallelCompact::SpaceId dest_space_id = PSParallelCompact::space_id(dest_addr); HeapWord* new_top = PSParallelCompact::new_top(dest_space_id); - assert(dest_addr < new_top, "sanity"); - - return MIN2(pointer_delta(new_top, dest_addr), ParallelCompactData::RegionSize); + return MIN2(pointer_delta(new_top, dest_addr), + ParallelCompactData::RegionSize); } inline diff --git a/src/hotspot/share/gc/shared/c2/barrierSetC2.hpp b/src/hotspot/share/gc/shared/c2/barrierSetC2.hpp index 00fbf1f2c9f8b..a78fd434ad94e 100644 --- a/src/hotspot/share/gc/shared/c2/barrierSetC2.hpp +++ b/src/hotspot/share/gc/shared/c2/barrierSetC2.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -92,9 +92,9 @@ class C2AccessValuePtr: public C2AccessValue { public: C2AccessValuePtr(Node* node, const TypePtr* type) : - C2AccessValue(node, reinterpret_cast(type)) {} + C2AccessValue(node, type) {} - const TypePtr* type() const { return reinterpret_cast(_type); } + const TypePtr* type() const { return _type->is_ptr(); } }; // This class wraps a bunch of context parameters that are passed around in the diff --git a/src/hotspot/share/gc/shared/preservedMarks.cpp b/src/hotspot/share/gc/shared/preservedMarks.cpp index 4daba541d29e8..bc241fb5daf45 100644 --- a/src/hotspot/share/gc/shared/preservedMarks.cpp +++ b/src/hotspot/share/gc/shared/preservedMarks.cpp @@ -143,10 +143,6 @@ void PreservedMarksSet::restore(WorkerThreads* workers) { assert_empty(); } -WorkerTask* PreservedMarksSet::create_task() { - return new RestorePreservedMarksTask(this); -} - void PreservedMarksSet::reclaim() { assert_empty(); diff --git a/src/hotspot/share/gc/shared/preservedMarks.hpp b/src/hotspot/share/gc/shared/preservedMarks.hpp index 3fc8c62bff16d..84bf18e41afb6 100644 --- a/src/hotspot/share/gc/shared/preservedMarks.hpp +++ b/src/hotspot/share/gc/shared/preservedMarks.hpp @@ -114,8 +114,6 @@ class PreservedMarksSet : public CHeapObj { // is null, perform the work serially in the current thread. void restore(WorkerThreads* workers); - WorkerTask* create_task(); - // Reclaim stack array. void reclaim(); diff --git a/src/hotspot/share/oops/compressedKlass.cpp b/src/hotspot/share/oops/compressedKlass.cpp index 0cfc0e3c60be3..f3c6fe92897be 100644 --- a/src/hotspot/share/oops/compressedKlass.cpp +++ b/src/hotspot/share/oops/compressedKlass.cpp @@ -154,8 +154,6 @@ void CompressedKlassPointers::calc_lowest_highest_narrow_klass_id() { // set this encoding scheme. Used by CDS at runtime to re-instate the scheme used to pre-compute klass ids for // archived heap objects. void CompressedKlassPointers::initialize_for_given_encoding(address addr, size_t len, address requested_base, int requested_shift) { - address const end = addr + len; - if (len > max_klass_range_size()) { stringStream ss; ss.print("Class space size and CDS archive size combined (%zu) " diff --git a/src/hotspot/share/opto/loopTransform.cpp b/src/hotspot/share/opto/loopTransform.cpp index b8150ab302e0a..06c43697350e2 100644 --- a/src/hotspot/share/opto/loopTransform.cpp +++ b/src/hotspot/share/opto/loopTransform.cpp @@ -1779,47 +1779,19 @@ bool IdealLoopTree::is_invariant(Node* n) const { // Search the Assertion Predicates added by loop predication and/or range check elimination and update them according // to the new stride. -void PhaseIdealLoop::update_main_loop_assertion_predicates(Node* ctrl, CountedLoopNode* loop_head, Node* init, - const int stride_con) { - Node* entry = ctrl; - Node* prev_proj = ctrl; - LoopNode* outer_loop_head = loop_head->skip_strip_mined(); - IdealLoopTree* outer_loop = get_loop(outer_loop_head); +void PhaseIdealLoop::update_main_loop_assertion_predicates(CountedLoopNode* main_loop_head) { + Node* init = main_loop_head->init_trip(); // Compute the value of the loop induction variable at the end of the // first iteration of the unrolled loop: init + new_stride_con - init_inc - int new_stride_con = stride_con * 2; - Node* max_value = _igvn.intcon(new_stride_con); - set_ctrl(max_value, C->root()); - - while (entry != nullptr && entry->is_Proj() && entry->in(0)->is_If()) { - IfNode* iff = entry->in(0)->as_If(); - ProjNode* proj = iff->proj_out(1 - entry->as_Proj()->_con); - if (!proj->unique_ctrl_out()->is_Halt()) { - break; - } - Node* bol = iff->in(1); - if (bol->is_OpaqueTemplateAssertionPredicate()) { - assert(assertion_predicate_has_loop_opaque_node(iff), "must find OpaqueLoop* nodes"); - // This is a Template Assertion Predicate for the initial or last access. - // Create an Initialized Assertion Predicates for it accordingly: - // - For the initial access a[init] (same as before) - // - For the last access a[init+new_stride-orig_stride] (with the new unroll stride) - prev_proj = create_initialized_assertion_predicate(iff, init, max_value, prev_proj); - } else if (bol->is_OpaqueInitializedAssertionPredicate()) { - // This is one of the two Initialized Assertion Predicates: - // - For the initial access a[init] - // - For the last access a[init+old_stride-orig_stride] - // We could keep the one for the initial access but we do not know which one we currently have here. Just kill both. - _igvn.replace_input_of(iff, 1, _igvn.intcon(1)); - } - assert(!bol->is_OpaqueNotNull() || !loop_head->is_main_loop(), "OpaqueNotNull should not be at main loop"); - entry = entry->in(0)->in(0); - } - if (prev_proj != ctrl) { - _igvn.replace_input_of(outer_loop_head, LoopNode::EntryControl, prev_proj); - set_idom(outer_loop_head, prev_proj, dom_depth(outer_loop_head)); - } + int unrolled_stride_con = main_loop_head->stride_con() * 2; + Node* unrolled_stride = _igvn.intcon(unrolled_stride_con); + set_ctrl(unrolled_stride, C->root()); + + Node* loop_entry = main_loop_head->skip_strip_mined()->in(LoopNode::EntryControl); + PredicateIterator predicate_iterator(loop_entry); + UpdateStrideForAssertionPredicates update_stride_for_assertion_predicates(unrolled_stride, this); + predicate_iterator.for_each(update_stride_for_assertion_predicates); } // Source Loop: Cloned - peeled_loop_head @@ -1936,7 +1908,7 @@ void PhaseIdealLoop::do_unroll(IdealLoopTree *loop, Node_List &old_new, bool adj assert(old_trip_count > 1 && (!adjust_min_trip || stride_p <= MIN2(max_jint / 2 - 2, MAX2(1<<3, Matcher::max_vector_size(T_BYTE)) * loop_head->unrolled_count())), "sanity"); - update_main_loop_assertion_predicates(ctrl, loop_head, init, stride_con); + update_main_loop_assertion_predicates(loop_head); // Adjust loop limit to keep valid iterations number after unroll. // Use (limit - stride) instead of (((limit - init)/stride) & (-2))*stride diff --git a/src/hotspot/share/opto/loopnode.hpp b/src/hotspot/share/opto/loopnode.hpp index d4f8e2e254a12..a28ccd628e8ac 100644 --- a/src/hotspot/share/opto/loopnode.hpp +++ b/src/hotspot/share/opto/loopnode.hpp @@ -948,7 +948,7 @@ class PhaseIdealLoop : public PhaseTransform { private: DEBUG_ONLY(static void count_opaque_loop_nodes(Node* n, uint& init, uint& stride);) static void get_assertion_predicates(ParsePredicateSuccessProj* parse_predicate_proj, Unique_Node_List& list, bool get_opaque = false); - void update_main_loop_assertion_predicates(Node* ctrl, CountedLoopNode* loop_head, Node* init, int stride_con); + void update_main_loop_assertion_predicates(CountedLoopNode* main_loop_head); void initialize_assertion_predicates_for_peeled_loop(CountedLoopNode* peeled_loop_head, CountedLoopNode* remaining_loop_head, uint first_node_index_in_cloned_loop_body, diff --git a/src/hotspot/share/opto/phaseX.cpp b/src/hotspot/share/opto/phaseX.cpp index f766146a894fc..c91a7bba07826 100644 --- a/src/hotspot/share/opto/phaseX.cpp +++ b/src/hotspot/share/opto/phaseX.cpp @@ -27,6 +27,7 @@ #include "gc/shared/c2/barrierSetC2.hpp" #include "memory/allocation.inline.hpp" #include "memory/resourceArea.hpp" +#include "opto/addnode.hpp" #include "opto/block.hpp" #include "opto/callnode.hpp" #include "opto/castnode.hpp" @@ -1634,12 +1635,19 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ } } } - // If changed AddP inputs, check Stores for loop invariant - if( use_op == Op_AddP ) { + // If changed AddP inputs: + // - check Stores for loop invariant, and + // - if the changed input is the offset, check constant-offset AddP users for + // address expression flattening. + if (use_op == Op_AddP) { + bool offset_changed = n == use->in(AddPNode::Offset); for (DUIterator_Fast i2max, i2 = use->fast_outs(i2max); i2 < i2max; i2++) { Node* u = use->fast_out(i2); - if (u->is_Mem()) + if (u->is_Mem()) { worklist.push(u); + } else if (offset_changed && u->is_AddP() && u->in(AddPNode::Offset)->is_Con()) { + worklist.push(u); + } } } // If changed initialization activity, check dependent Stores diff --git a/src/hotspot/share/opto/predicates.cpp b/src/hotspot/share/opto/predicates.cpp index ee21806356709..e8287299d0da7 100644 --- a/src/hotspot/share/opto/predicates.cpp +++ b/src/hotspot/share/opto/predicates.cpp @@ -152,7 +152,6 @@ void TemplateAssertionPredicate::rewire_loop_data_dependencies(IfTrueNode* targe } } - // Template Assertion Predicates always have the dedicated OpaqueTemplateAssertionPredicate to identify them. bool TemplateAssertionPredicate::is_predicate(Node* node) { if (!may_be_assertion_predicate_if(node)) { @@ -179,6 +178,24 @@ IfTrueNode* TemplateAssertionPredicate::clone_and_replace_init(Node* new_control return success_proj; } +// Replace the input to OpaqueLoopStrideNode with 'new_stride' and leave the other nodes unchanged. +void TemplateAssertionPredicate::replace_opaque_stride_input(Node* new_stride, PhaseIterGVN& igvn) const { + TemplateAssertionExpression expression(opaque_node()); + expression.replace_opaque_stride_input(new_stride, igvn); +} + +// Create a new Initialized Assertion Predicate from this template at 'new_control' and return the success projection +// of the newly created Initialized Assertion Predicate. +IfTrueNode* TemplateAssertionPredicate::initialize(PhaseIdealLoop* phase, Node* new_control) const { + assert(phase->assertion_predicate_has_loop_opaque_node(head()), + "must find OpaqueLoop* nodes for Template Assertion Predicate"); + InitializedAssertionPredicateCreator initialized_assertion_predicate(phase); + IfTrueNode* success_proj = initialized_assertion_predicate.create_from_template(head(), new_control); + assert(!phase->assertion_predicate_has_loop_opaque_node(success_proj->in(0)->as_If()), + "Initialized Assertion Predicates do not have OpaqueLoop* nodes in the bool expression anymore"); + return success_proj; +} + // Initialized Assertion Predicates always have the dedicated OpaqueInitiailizedAssertionPredicate node to identify // them. bool InitializedAssertionPredicate::is_predicate(Node* node) { @@ -189,6 +206,12 @@ bool InitializedAssertionPredicate::is_predicate(Node* node) { return if_node->in(1)->is_OpaqueInitializedAssertionPredicate(); } +void InitializedAssertionPredicate::kill(PhaseIdealLoop* phase) const { + Node* true_con = phase->igvn().intcon(1); + phase->set_ctrl(true_con, phase->C->root()); + phase->igvn().replace_input_of(_if_node, 1, true_con); +} + #ifdef ASSERT // Check that the block has at most one Parse Predicate and that we only find Regular Predicate nodes (i.e. IfProj, // If, or RangeCheck nodes). @@ -388,6 +411,63 @@ TemplateAssertionExpression::clone(const TransformStrategyForOpaqueLoopNodes& tr return opaque_node_clone->as_OpaqueTemplateAssertionPredicate(); } +// This class is used to replace the input to OpaqueLoopStrideNode with a new node while leaving the other nodes +// unchanged. +class ReplaceOpaqueStrideInput : public StackObj { + PhaseIterGVN& _igvn; + Unique_Node_List _nodes_to_visit; + + public: + ReplaceOpaqueStrideInput(OpaqueTemplateAssertionPredicateNode* start_node, PhaseIterGVN& igvn) : _igvn(igvn) { + _nodes_to_visit.push(start_node); + } + NONCOPYABLE(ReplaceOpaqueStrideInput); + + void replace(Node* new_opaque_stride_input) { + for (uint i = 0; i < _nodes_to_visit.size(); i++) { + Node* next = _nodes_to_visit[i]; + for (uint j = 1; j < next->req(); j++) { + Node* input = next->in(j); + if (input->is_OpaqueLoopStride()) { + assert(TemplateAssertionExpressionNode::is_maybe_in_expression(input), "must also pass node filter"); + _igvn.replace_input_of(input, 1, new_opaque_stride_input); + } else if (TemplateAssertionExpressionNode::is_maybe_in_expression(input)) { + _nodes_to_visit.push(input); + } + } + } + } +}; + +// Replace the input to OpaqueLoopStrideNode with 'new_stride' and leave the other nodes unchanged. +void TemplateAssertionExpression::replace_opaque_stride_input(Node* new_stride, PhaseIterGVN& igvn) { + ReplaceOpaqueStrideInput replace_opaque_stride_input(_opaque_node, igvn); + replace_opaque_stride_input.replace(new_stride); +} + +// The transformations of this class fold the OpaqueLoop* nodes by returning their inputs. +class RemoveOpaqueLoopNodesStrategy : public TransformStrategyForOpaqueLoopNodes { + public: + Node* transform_opaque_init(OpaqueLoopInitNode* opaque_init) const override { + return opaque_init->in(1); + } + + Node* transform_opaque_stride(OpaqueLoopStrideNode* opaque_stride) const override { + return opaque_stride->in(1); + } +}; + +OpaqueInitializedAssertionPredicateNode* +TemplateAssertionExpression::clone_and_fold_opaque_loop_nodes(Node* new_control, PhaseIdealLoop* phase) { + RemoveOpaqueLoopNodesStrategy remove_opaque_loop_nodes_strategy; + OpaqueTemplateAssertionPredicateNode* cloned_template_opaque = clone(remove_opaque_loop_nodes_strategy, new_control, + phase); + OpaqueInitializedAssertionPredicateNode* opaque_initialized_opaque = + new OpaqueInitializedAssertionPredicateNode(cloned_template_opaque->in(1)->as_Bool(), phase->C); + phase->register_new_node(opaque_initialized_opaque, new_control); + return opaque_initialized_opaque; +} + // Check if this node belongs a Template Assertion Expression (including OpaqueLoop* nodes). bool TemplateAssertionExpressionNode::is_in_expression(Node* node) { if (is_maybe_in_expression(node)) { @@ -664,6 +744,19 @@ IfTrueNode* InitializedAssertionPredicateCreator::create_from_template(IfNode* t NOT_PRODUCT(COMMA template_assertion_predicate->assertion_predicate_type())); } +// Create a new Initialized Assertion Predicate from 'template_assertion_predicate' by cloning it but omitting the +// OpaqueLoop*Notes (i.e. taking their inputs instead). +IfTrueNode* InitializedAssertionPredicateCreator::create_from_template(IfNode* template_assertion_predicate, + Node* new_control) { + OpaqueTemplateAssertionPredicateNode* template_opaque = + template_assertion_predicate->in(1)->as_OpaqueTemplateAssertionPredicate(); + TemplateAssertionExpression template_assertion_expression(template_opaque); + OpaqueInitializedAssertionPredicateNode* assertion_expression = + template_assertion_expression.clone_and_fold_opaque_loop_nodes(new_control, _phase); + return create_control_nodes(new_control, template_assertion_predicate->Opcode(), assertion_expression + NOT_PRODUCT(COMMA template_assertion_predicate->assertion_predicate_type())); +} + // Create a new Initialized Assertion Predicate directly without a template. IfTrueNode* InitializedAssertionPredicateCreator::create(Node* operand, Node* new_control, const jint stride, const int scale, Node* offset, Node* range NOT_PRODUCT(COMMA @@ -768,7 +861,7 @@ void CreateAssertionPredicatesVisitor::visit(const TemplateAssertionPredicate& t // Create an Initialized Assertion Predicate from the provided Template Assertion Predicate. IfTrueNode* CreateAssertionPredicatesVisitor::initialize_from_template( -const TemplateAssertionPredicate& template_assertion_predicate) const { + const TemplateAssertionPredicate& template_assertion_predicate) const { IfNode* template_head = template_assertion_predicate.head(); IfTrueNode* initialized_predicate = _phase->create_initialized_assertion_predicate(template_head, _init, _stride, _new_control); @@ -783,3 +876,36 @@ IfTrueNode* CreateAssertionPredicatesVisitor::clone_template_and_replace_init_in _phase->register_new_node(opaque_init, _new_control); return template_assertion_predicate.clone_and_replace_init(_new_control, opaque_init, _phase); } + +// Clone the Template Assertion Predicate and set a new input for the OpaqueLoopStrideNode. +void UpdateStrideForAssertionPredicates::visit(const TemplateAssertionPredicate& template_assertion_predicate) { + replace_opaque_stride_input(template_assertion_predicate); + Node* template_tail_control_out = template_assertion_predicate.tail()->unique_ctrl_out(); + IfTrueNode* initialized_success_proj = initialize_from_updated_template(template_assertion_predicate); + connect_initialized_assertion_predicate(template_tail_control_out, initialized_success_proj); +} + +// Replace the input to OpaqueLoopStrideNode with 'new_stride' and leave the other nodes unchanged. +void UpdateStrideForAssertionPredicates::replace_opaque_stride_input( + const TemplateAssertionPredicate& template_assertion_predicate) const { + template_assertion_predicate.replace_opaque_stride_input(_new_stride, _phase->igvn()); +} + +IfTrueNode* UpdateStrideForAssertionPredicates::initialize_from_updated_template( + const TemplateAssertionPredicate& template_assertion_predicate) const { + IfTrueNode* initialized_success_proj = template_assertion_predicate.initialize(_phase, template_assertion_predicate.tail()); + return initialized_success_proj; +} + +// The newly created Initialized Assertion Predicate can safely be inserted because this visitor is already visiting +// the Template Assertion Predicate above this. So, we will not accidentally visit this again and kill it with the +// visit() method for Initialized Assertion Predicates. +void UpdateStrideForAssertionPredicates::connect_initialized_assertion_predicate( + Node* new_control_out, IfTrueNode* initialized_success_proj) const { + if (new_control_out->is_Loop()) { + _phase->igvn().replace_input_of(new_control_out, LoopNode::EntryControl, initialized_success_proj); + } else { + _phase->igvn().replace_input_of(new_control_out, 0, initialized_success_proj); + } + _phase->set_idom(new_control_out, initialized_success_proj, _phase->dom_depth(new_control_out)); +} diff --git a/src/hotspot/share/opto/predicates.hpp b/src/hotspot/share/opto/predicates.hpp index dc834a18399d2..d7171db7115cf 100644 --- a/src/hotspot/share/opto/predicates.hpp +++ b/src/hotspot/share/opto/predicates.hpp @@ -404,6 +404,8 @@ class TemplateAssertionPredicate : public Predicate { } IfTrueNode* clone_and_replace_init(Node* new_control, OpaqueLoopInitNode* new_opaque_init, PhaseIdealLoop* phase) const; + void replace_opaque_stride_input(Node* new_stride, PhaseIterGVN& igvn) const; + IfTrueNode* initialize(PhaseIdealLoop* phase, Node* new_control) const; void rewire_loop_data_dependencies(IfTrueNode* target_predicate, const NodeInLoopBody& data_in_loop_body, PhaseIdealLoop* phase) const; static bool is_predicate(Node* node); @@ -434,6 +436,7 @@ class InitializedAssertionPredicate : public Predicate { return _success_proj; } + void kill(PhaseIdealLoop* phase) const; static bool is_predicate(Node* node); }; @@ -461,6 +464,8 @@ class TemplateAssertionExpression : public StackObj { OpaqueTemplateAssertionPredicateNode* clone_and_replace_init(Node* new_init, Node* new_ctrl, PhaseIdealLoop* phase); OpaqueTemplateAssertionPredicateNode* clone_and_replace_init_and_stride(Node* new_control, Node* new_init, Node* new_stride, PhaseIdealLoop* phase); + void replace_opaque_stride_input(Node* new_stride, PhaseIterGVN& igvn); + OpaqueInitializedAssertionPredicateNode* clone_and_fold_opaque_loop_nodes(Node* new_ctrl, PhaseIdealLoop* phase); }; // Class to represent a node being part of a Template Assertion Expression. Note that this is not an IR node. @@ -608,6 +613,7 @@ class InitializedAssertionPredicateCreator : public StackObj { IfTrueNode* create_from_template(IfNode* template_assertion_predicate, Node* new_control, Node* new_init, Node* new_stride); + IfTrueNode* create_from_template(IfNode* template_assertion_predicate, Node* new_control); IfTrueNode* create(Node* operand, Node* new_control, jint stride, int scale, Node* offset, Node* range NOT_PRODUCT(COMMA AssertionPredicateType assertion_predicate_type)); @@ -1032,4 +1038,32 @@ class TemplateAssertionPredicateCollector : public PredicateVisitor { } }; +// This visitor updates the stride for an Assertion Predicate during Loop Unrolling. The inputs to the OpaqueLoopStride +// nodes Template of Template Assertion Predicates are updated and new Initialized Assertion Predicates are created +// from the updated templates. The old Initialized Assertion Predicates are killed. +class UpdateStrideForAssertionPredicates : public PredicateVisitor { + Node* const _new_stride; + PhaseIdealLoop* const _phase; + + void replace_opaque_stride_input(const TemplateAssertionPredicate& template_assertion_predicate) const; + IfTrueNode* initialize_from_updated_template(const TemplateAssertionPredicate& template_assertion_predicate) const; + void connect_initialized_assertion_predicate(Node* new_control_out, IfTrueNode* initialized_success_proj) const; + + public: + UpdateStrideForAssertionPredicates(Node* const new_stride, PhaseIdealLoop* phase) + : _new_stride(new_stride), + _phase(phase) {} + NONCOPYABLE(UpdateStrideForAssertionPredicates); + + using PredicateVisitor::visit; + + void visit(const TemplateAssertionPredicate& template_assertion_predicate) override; + + // Kill the old Initialized Assertion Predicates with old strides before unrolling. The new Initialized Assertion + // Predicates are inserted after the Template Assertion Predicate which ensures that we are not accidentally visiting + // and killing a newly created Initialized Assertion Predicate here. + void visit(const InitializedAssertionPredicate& initialized_assertion_predicate) override { + initialized_assertion_predicate.kill(_phase); + } +}; #endif // SHARE_OPTO_PREDICATES_HPP diff --git a/src/hotspot/share/runtime/lockStack.inline.hpp b/src/hotspot/share/runtime/lockStack.inline.hpp index 55411c0d24790..0516a85356d1f 100644 --- a/src/hotspot/share/runtime/lockStack.inline.hpp +++ b/src/hotspot/share/runtime/lockStack.inline.hpp @@ -239,7 +239,9 @@ inline void LockStack::move_from_address(oop* start, int count) { } inline void LockStack::oops_do(OopClosure* cl) { - verify("pre-oops-do"); + // We don't perform pre oops_do verify here because this function + // is used by the GC to fix the oops. + int end = to_index(_top); for (int i = 0; i < end; i++) { cl->do_oop(&_base[i]); diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index b40f627441230..fd9dcf60e540e 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -830,9 +830,9 @@ public AbstractStringBuilder append(int i) { int spaceNeeded = count + DecimalDigits.stringSize(i); ensureCapacityInternal(spaceNeeded); if (isLatin1()) { - StringLatin1.getChars(i, spaceNeeded, value); + DecimalDigits.getCharsLatin1(i, spaceNeeded, value); } else { - StringUTF16.getChars(i, count, spaceNeeded, value); + DecimalDigits.getCharsUTF16(i, spaceNeeded, value); } this.count = spaceNeeded; return this; @@ -855,9 +855,9 @@ public AbstractStringBuilder append(long l) { int spaceNeeded = count + DecimalDigits.stringSize(l); ensureCapacityInternal(spaceNeeded); if (isLatin1()) { - StringLatin1.getChars(l, spaceNeeded, value); + DecimalDigits.getCharsLatin1(l, spaceNeeded, value); } else { - StringUTF16.getChars(l, count, spaceNeeded, value); + DecimalDigits.getCharsUTF16(l, spaceNeeded, value); } this.count = spaceNeeded; return this; diff --git a/src/java.base/share/classes/java/lang/Double.java b/src/java.base/share/classes/java/lang/Double.java index ed23f7d39c9fc..8f7b6c6aa4234 100644 --- a/src/java.base/share/classes/java/lang/Double.java +++ b/src/java.base/share/classes/java/lang/Double.java @@ -375,9 +375,9 @@ public final class Double extends Number public static final double NEGATIVE_INFINITY = -1.0 / 0.0; /** - * A constant holding a Not-a-Number (NaN) value of type - * {@code double}. It is equivalent to the value returned by - * {@code Double.longBitsToDouble(0x7ff8000000000000L)}. + * A constant holding a Not-a-Number (NaN) value of type {@code double}. + * It is {@linkplain Double##equivalenceRelation equivalent} to the + * value returned by {@code Double.longBitsToDouble(0x7ff8000000000000L)}. */ public static final double NaN = 0.0d / 0.0; diff --git a/src/java.base/share/classes/java/lang/Float.java b/src/java.base/share/classes/java/lang/Float.java index 821a05fa00abd..85b20e6a2e1ec 100644 --- a/src/java.base/share/classes/java/lang/Float.java +++ b/src/java.base/share/classes/java/lang/Float.java @@ -93,9 +93,9 @@ public final class Float extends Number public static final float NEGATIVE_INFINITY = -1.0f / 0.0f; /** - * A constant holding a Not-a-Number (NaN) value of type - * {@code float}. It is equivalent to the value returned by - * {@code Float.intBitsToFloat(0x7fc00000)}. + * A constant holding a Not-a-Number (NaN) value of type {@code float}. + * It is {@linkplain Double##equivalenceRelation equivalent} + * to the value returned by{@code Float.intBitsToFloat(0x7fc00000)}. */ public static final float NaN = 0.0f / 0.0f; diff --git a/src/java.base/share/classes/java/lang/Integer.java b/src/java.base/share/classes/java/lang/Integer.java index e666e977c61a5..5f73d61e5d472 100644 --- a/src/java.base/share/classes/java/lang/Integer.java +++ b/src/java.base/share/classes/java/lang/Integer.java @@ -432,11 +432,11 @@ public static String toString(int i) { int size = DecimalDigits.stringSize(i); if (COMPACT_STRINGS) { byte[] buf = new byte[size]; - StringLatin1.getChars(i, size, buf); + DecimalDigits.getCharsLatin1(i, size, buf); return new String(buf, LATIN1); } else { byte[] buf = new byte[size * 2]; - StringUTF16.getChars(i, size, buf); + DecimalDigits.getCharsUTF16(i, size, buf); return new String(buf, UTF16); } } diff --git a/src/java.base/share/classes/java/lang/Long.java b/src/java.base/share/classes/java/lang/Long.java index 8c083b3ec8431..7df9ddfb2708b 100644 --- a/src/java.base/share/classes/java/lang/Long.java +++ b/src/java.base/share/classes/java/lang/Long.java @@ -462,11 +462,11 @@ public static String toString(long i) { int size = DecimalDigits.stringSize(i); if (COMPACT_STRINGS) { byte[] buf = new byte[size]; - StringLatin1.getChars(i, size, buf); + DecimalDigits.getCharsLatin1(i, size, buf); return new String(buf, LATIN1); } else { byte[] buf = new byte[size * 2]; - StringUTF16.getChars(i, size, buf); + DecimalDigits.getCharsUTF16(i, size, buf); return new String(buf, UTF16); } } diff --git a/src/java.base/share/classes/java/lang/StringConcatHelper.java b/src/java.base/share/classes/java/lang/StringConcatHelper.java index d0c558ed93a1a..632fe0f58b58b 100644 --- a/src/java.base/share/classes/java/lang/StringConcatHelper.java +++ b/src/java.base/share/classes/java/lang/StringConcatHelper.java @@ -236,17 +236,10 @@ static long prepend(long indexCoder, byte[] buf, boolean value, String prefix) { if (indexCoder < UTF16) { if (value) { index -= 4; - buf[index] = 't'; - buf[index + 1] = 'r'; - buf[index + 2] = 'u'; - buf[index + 3] = 'e'; + StringLatin1.putCharsAt(buf, index, 't', 'r', 'u', 'e'); } else { index -= 5; - buf[index] = 'f'; - buf[index + 1] = 'a'; - buf[index + 2] = 'l'; - buf[index + 3] = 's'; - buf[index + 4] = 'e'; + StringLatin1.putCharsAt(buf, index, 'f', 'a', 'l', 's', 'e'); } index -= prefix.length(); prefix.getBytes(buf, index, String.LATIN1); @@ -254,17 +247,10 @@ static long prepend(long indexCoder, byte[] buf, boolean value, String prefix) { } else { if (value) { index -= 4; - StringUTF16.putChar(buf, index, 't'); - StringUTF16.putChar(buf, index + 1, 'r'); - StringUTF16.putChar(buf, index + 2, 'u'); - StringUTF16.putChar(buf, index + 3, 'e'); + StringUTF16.putCharsAt(buf, index, 't', 'r', 'u', 'e'); } else { index -= 5; - StringUTF16.putChar(buf, index, 'f'); - StringUTF16.putChar(buf, index + 1, 'a'); - StringUTF16.putChar(buf, index + 2, 'l'); - StringUTF16.putChar(buf, index + 3, 's'); - StringUTF16.putChar(buf, index + 4, 'e'); + StringUTF16.putCharsAt(buf, index, 'f', 'a', 'l', 's', 'e'); } index -= prefix.length(); prefix.getBytes(buf, index, String.UTF16); @@ -312,12 +298,12 @@ static long prepend(long indexCoder, byte[] buf, char value, String prefix) { static long prepend(long indexCoder, byte[] buf, int value, String prefix) { int index = (int)indexCoder; if (indexCoder < UTF16) { - index = StringLatin1.getChars(value, index, buf); + index = DecimalDigits.getCharsLatin1(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.LATIN1); return index; } else { - index = StringUTF16.getChars(value, index, buf); + index = DecimalDigits.getCharsUTF16(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.UTF16); return index | UTF16; @@ -338,12 +324,12 @@ static long prepend(long indexCoder, byte[] buf, int value, String prefix) { static long prepend(long indexCoder, byte[] buf, long value, String prefix) { int index = (int)indexCoder; if (indexCoder < UTF16) { - index = StringLatin1.getChars(value, index, buf); + index = DecimalDigits.getCharsLatin1(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.LATIN1); return index; } else { - index = StringUTF16.getChars(value, index, buf); + index = DecimalDigits.getCharsUTF16(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.UTF16); return index | UTF16; @@ -638,34 +624,20 @@ static int prepend(int index, byte coder, byte[] buf, boolean value, String pref if (coder == String.LATIN1) { if (value) { index -= 4; - buf[index] = 't'; - buf[index + 1] = 'r'; - buf[index + 2] = 'u'; - buf[index + 3] = 'e'; + StringLatin1.putCharsAt(buf, index, 't', 'r', 'u', 'e'); } else { index -= 5; - buf[index] = 'f'; - buf[index + 1] = 'a'; - buf[index + 2] = 'l'; - buf[index + 3] = 's'; - buf[index + 4] = 'e'; + StringLatin1.putCharsAt(buf, index, 'f', 'a', 'l', 's', 'e'); } index -= prefix.length(); prefix.getBytes(buf, index, String.LATIN1); } else { if (value) { index -= 4; - StringUTF16.putChar(buf, index, 't'); - StringUTF16.putChar(buf, index + 1, 'r'); - StringUTF16.putChar(buf, index + 2, 'u'); - StringUTF16.putChar(buf, index + 3, 'e'); + StringUTF16.putCharsAt(buf, index, 't', 'r', 'u', 'e'); } else { index -= 5; - StringUTF16.putChar(buf, index, 'f'); - StringUTF16.putChar(buf, index + 1, 'a'); - StringUTF16.putChar(buf, index + 2, 'l'); - StringUTF16.putChar(buf, index + 3, 's'); - StringUTF16.putChar(buf, index + 4, 'e'); + StringUTF16.putCharsAt(buf, index, 'f', 'a', 'l', 's', 'e'); } index -= prefix.length(); prefix.getBytes(buf, index, String.UTF16); @@ -710,11 +682,11 @@ static int prepend(int index, byte coder, byte[] buf, char value, String prefix) */ static int prepend(int index, byte coder, byte[] buf, int value, String prefix) { if (coder == String.LATIN1) { - index = StringLatin1.getChars(value, index, buf); + index = DecimalDigits.getCharsLatin1(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.LATIN1); } else { - index = StringUTF16.getChars(value, index, buf); + index = DecimalDigits.getCharsUTF16(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.UTF16); } @@ -734,11 +706,11 @@ static int prepend(int index, byte coder, byte[] buf, int value, String prefix) */ static int prepend(int index, byte coder, byte[] buf, long value, String prefix) { if (coder == String.LATIN1) { - index = StringLatin1.getChars(value, index, buf); + index = DecimalDigits.getCharsLatin1(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.LATIN1); } else { - index = StringUTF16.getChars(value, index, buf); + index = DecimalDigits.getCharsUTF16(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.UTF16); } diff --git a/src/java.base/share/classes/java/lang/StringLatin1.java b/src/java.base/share/classes/java/lang/StringLatin1.java index c12b8afc21f37..abe42c2c7c72f 100644 --- a/src/java.base/share/classes/java/lang/StringLatin1.java +++ b/src/java.base/share/classes/java/lang/StringLatin1.java @@ -34,7 +34,6 @@ import java.util.stream.StreamSupport; import jdk.internal.misc.Unsafe; import jdk.internal.util.ArraysSupport; -import jdk.internal.util.DecimalDigits; import jdk.internal.vm.annotation.IntrinsicCandidate; import static java.lang.String.LATIN1; @@ -86,120 +85,6 @@ public static byte[] inflate(byte[] value, int off, int len) { return ret; } - /** - * Places characters representing the integer i into the - * character array buf. The characters are placed into - * the buffer backwards starting with the least significant - * digit at the specified index (exclusive), and working - * backwards from there. - * - * @implNote This method converts positive inputs into negative - * values, to cover the Integer.MIN_VALUE case. Converting otherwise - * (negative to positive) will expose -Integer.MIN_VALUE that overflows - * integer. - * - * @param i value to convert - * @param index next index, after the least significant digit - * @param buf target buffer, Latin1-encoded - * @return index of the most significant digit or minus sign, if present - */ - static int getChars(int i, int index, byte[] buf) { - // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. - int q; - int charPos = index; - - boolean negative = i < 0; - if (!negative) { - i = -i; - } - - // Generate two digits per iteration - while (i <= -100) { - q = i / 100; - charPos -= 2; - writeDigitPair(buf, charPos, (q * 100) - i); - i = q; - } - - // We know there are at most two digits left at this point. - if (i < -9) { - charPos -= 2; - writeDigitPair(buf, charPos, -i); - } else { - buf[--charPos] = (byte)('0' - i); - } - - if (negative) { - buf[--charPos] = (byte)'-'; - } - return charPos; - } - - /** - * Places characters representing the long i into the - * character array buf. The characters are placed into - * the buffer backwards starting with the least significant - * digit at the specified index (exclusive), and working - * backwards from there. - * - * @implNote This method converts positive inputs into negative - * values, to cover the Long.MIN_VALUE case. Converting otherwise - * (negative to positive) will expose -Long.MIN_VALUE that overflows - * long. - * - * @param i value to convert - * @param index next index, after the least significant digit - * @param buf target buffer, Latin1-encoded - * @return index of the most significant digit or minus sign, if present - */ - static int getChars(long i, int index, byte[] buf) { - // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. - long q; - int charPos = index; - - boolean negative = (i < 0); - if (!negative) { - i = -i; - } - - // Get 2 digits/iteration using longs until quotient fits into an int - while (i <= Integer.MIN_VALUE) { - q = i / 100; - charPos -= 2; - writeDigitPair(buf, charPos, (int)((q * 100) - i)); - i = q; - } - - // Get 2 digits/iteration using ints - int q2; - int i2 = (int)i; - while (i2 <= -100) { - q2 = i2 / 100; - charPos -= 2; - writeDigitPair(buf, charPos, (q2 * 100) - i2); - i2 = q2; - } - - // We know there are at most two digits left at this point. - if (i2 < -9) { - charPos -= 2; - writeDigitPair(buf, charPos, -i2); - } else { - buf[--charPos] = (byte)('0' - i2); - } - - if (negative) { - buf[--charPos] = (byte)'-'; - } - return charPos; - } - - private static void writeDigitPair(byte[] buf, int charPos, int value) { - short pair = DecimalDigits.digitPair(value); - buf[charPos] = (byte)(pair); - buf[charPos + 1] = (byte)(pair >> 8); - } - public static void getChars(byte[] value, int srcBegin, int srcEnd, char[] dst, int dstBegin) { inflate(value, srcBegin, dst, dstBegin, srcEnd - srcBegin); } diff --git a/src/java.base/share/classes/java/lang/StringUTF16.java b/src/java.base/share/classes/java/lang/StringUTF16.java index f04b991827f7f..a1dcca8ffad18 100644 --- a/src/java.base/share/classes/java/lang/StringUTF16.java +++ b/src/java.base/share/classes/java/lang/StringUTF16.java @@ -35,7 +35,6 @@ import jdk.internal.misc.Unsafe; import jdk.internal.util.ArraysSupport; -import jdk.internal.util.DecimalDigits; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; @@ -1513,20 +1512,6 @@ public static int codePointCountSB(byte[] val, int beginIndex, int endIndex) { return codePointCount(val, beginIndex, endIndex, true /* checked */); } - public static int getChars(int i, int begin, int end, byte[] value) { - checkBoundsBeginEnd(begin, end, value); - int pos = getChars(i, end, value); - assert begin == pos; - return pos; - } - - public static int getChars(long l, int begin, int end, byte[] value) { - checkBoundsBeginEnd(begin, end, value); - int pos = getChars(l, end, value); - assert begin == pos; - return pos; - } - public static boolean contentEquals(byte[] v1, byte[] v2, int len) { checkBoundsOffCount(0, len, v2); for (int i = 0; i < len; i++) { @@ -1662,109 +1647,6 @@ public static int lastIndexOfLatin1(byte[] src, int srcCount, static final int MAX_LENGTH = Integer.MAX_VALUE >> 1; - // Used by trusted callers. Assumes all necessary bounds checks have - // been done by the caller. - - /** - * This is a variant of {@link StringLatin1#getChars(int, int, byte[])}, but for - * UTF-16 coder. - * - * @param i value to convert - * @param index next index, after the least significant digit - * @param buf target buffer, UTF16-coded. - * @return index of the most significant digit or minus sign, if present - */ - static int getChars(int i, int index, byte[] buf) { - // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. - int q, r; - int charPos = index; - - boolean negative = (i < 0); - if (!negative) { - i = -i; - } - - // Get 2 digits/iteration using ints - while (i <= -100) { - q = i / 100; - r = (q * 100) - i; - i = q; - charPos -= 2; - putPair(buf, charPos, r); - } - - // We know there are at most two digits left at this point. - if (i < -9) { - charPos -= 2; - putPair(buf, charPos, -i); - } else { - putChar(buf, --charPos, '0' - i); - } - - if (negative) { - putChar(buf, --charPos, '-'); - } - return charPos; - } - - /** - * This is a variant of {@link StringLatin1#getChars(long, int, byte[])}, but for - * UTF-16 coder. - * - * @param i value to convert - * @param index next index, after the least significant digit - * @param buf target buffer, UTF16-coded. - * @return index of the most significant digit or minus sign, if present - */ - static int getChars(long i, int index, byte[] buf) { - // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. - long q; - int charPos = index; - - boolean negative = (i < 0); - if (!negative) { - i = -i; - } - - // Get 2 digits/iteration using longs until quotient fits into an int - while (i <= Integer.MIN_VALUE) { - q = i / 100; - charPos -= 2; - putPair(buf, charPos, (int)((q * 100) - i)); - i = q; - } - - // Get 2 digits/iteration using ints - int q2; - int i2 = (int)i; - while (i2 <= -100) { - q2 = i2 / 100; - charPos -= 2; - putPair(buf, charPos, (q2 * 100) - i2); - i2 = q2; - } - - // We know there are at most two digits left at this point. - if (i2 < -9) { - charPos -= 2; - putPair(buf, charPos, -i2); - } else { - putChar(buf, --charPos, '0' - i2); - } - - if (negative) { - putChar(buf, --charPos, '-'); - } - return charPos; - } - - private static void putPair(byte[] buf, int charPos, int v) { - int packed = (int) DecimalDigits.digitPair(v); - putChar(buf, charPos, packed & 0xFF); - putChar(buf, charPos + 1, packed >> 8); - } - // End of trusted methods. - public static void checkIndex(int off, byte[] val) { String.checkIndex(off, length(val)); } diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index 5b04bca4f44af..451ed8e6bfc59 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -2648,14 +2648,6 @@ public byte stringCoder(String str) { return str.coder(); } - public int getCharsLatin1(long i, int index, byte[] buf) { - return StringLatin1.getChars(i, index, buf); - } - - public int getCharsUTF16(long i, int index, byte[] buf) { - return StringUTF16.getChars(i, index, buf); - } - public String join(String prefix, String suffix, String delimiter, String[] elements, int size) { return String.join(prefix, suffix, delimiter, elements, size); } diff --git a/src/java.base/share/classes/java/math/BigDecimal.java b/src/java.base/share/classes/java/math/BigDecimal.java index abd49aa69bccf..b00970963b6b3 100644 --- a/src/java.base/share/classes/java/math/BigDecimal.java +++ b/src/java.base/share/classes/java/math/BigDecimal.java @@ -35,9 +35,15 @@ import java.io.ObjectInputStream; import java.io.ObjectStreamException; import java.io.StreamCorruptedException; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Objects; +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.util.DecimalDigits; + /** * Immutable, arbitrary-precision signed decimal numbers. A {@code * BigDecimal} consists of an arbitrary precision integer @@ -328,6 +334,8 @@ * @since 1.1 */ public class BigDecimal extends Number implements Comparable { + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + /* * Let l = log_2(10). * Then, L < l < L + ulp(L) / 2, that is, L = roundTiesToEven(l). @@ -4164,103 +4172,6 @@ public BigDecimal ulp() { return BigDecimal.valueOf(1, this.scale(), 1); } - // Private class to build a string representation for BigDecimal object. The - // StringBuilder field acts as a buffer to hold the temporary representation - // of BigDecimal. The cmpCharArray holds all the characters for the compact - // representation of BigDecimal (except for '-' sign' if it is negative) if - // its intCompact field is not INFLATED. - static class StringBuilderHelper { - final StringBuilder sb; // Placeholder for BigDecimal string - final char[] cmpCharArray; // character array to place the intCompact - - StringBuilderHelper() { - sb = new StringBuilder(32); - // All non negative longs can be made to fit into 19 character array. - cmpCharArray = new char[19]; - } - - // Accessors. - StringBuilder getStringBuilder() { - sb.setLength(0); - return sb; - } - - char[] getCompactCharArray() { - return cmpCharArray; - } - - /** - * Places characters representing the intCompact in {@code long} into - * cmpCharArray and returns the offset to the array where the - * representation starts. - * - * @param intCompact the number to put into the cmpCharArray. - * @return offset to the array where the representation starts. - * Note: intCompact must be greater or equal to zero. - */ - int putIntCompact(long intCompact) { - assert intCompact >= 0; - - long q; - int r; - // since we start from the least significant digit, charPos points to - // the last character in cmpCharArray. - int charPos = cmpCharArray.length; - - // Get 2 digits/iteration using longs until quotient fits into an int - while (intCompact > Integer.MAX_VALUE) { - q = intCompact / 100; - r = (int)(intCompact - q * 100); - intCompact = q; - cmpCharArray[--charPos] = DIGIT_ONES[r]; - cmpCharArray[--charPos] = DIGIT_TENS[r]; - } - - // Get 2 digits/iteration using ints when i2 >= 100 - int q2; - int i2 = (int)intCompact; - while (i2 >= 100) { - q2 = i2 / 100; - r = i2 - q2 * 100; - i2 = q2; - cmpCharArray[--charPos] = DIGIT_ONES[r]; - cmpCharArray[--charPos] = DIGIT_TENS[r]; - } - - cmpCharArray[--charPos] = DIGIT_ONES[i2]; - if (i2 >= 10) - cmpCharArray[--charPos] = DIGIT_TENS[i2]; - - return charPos; - } - - static final char[] DIGIT_TENS = { - '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', - '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', - '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', - '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', - '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', - '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', - '6', '6', '6', '6', '6', '6', '6', '6', '6', '6', - '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', - '8', '8', '8', '8', '8', '8', '8', '8', '8', '8', - '9', '9', '9', '9', '9', '9', '9', '9', '9', '9', - }; - - static final char[] DIGIT_ONES = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - }; - } - /** * Lay out this {@code BigDecimal} into a {@code char[]} array. * The Java 1.2 equivalent to this was called {@code getValueString}. @@ -4271,6 +4182,8 @@ int putIntCompact(long intCompact) { * {@code BigDecimal} */ private String layoutChars(boolean sci) { + long intCompact = this.intCompact; + int scale = this.scale; if (scale == 0) // zero scale is trivial return (intCompact != INFLATED) ? Long.toString(intCompact): @@ -4280,18 +4193,24 @@ private String layoutChars(boolean sci) { // currency fast path int lowInt = (int)intCompact % 100; int highInt = (int)intCompact / 100; - return (Integer.toString(highInt) + '.' + - StringBuilderHelper.DIGIT_TENS[lowInt] + - StringBuilderHelper.DIGIT_ONES[lowInt]) ; + int highIntSize = DecimalDigits.stringSize(highInt); + byte[] buf = new byte[highIntSize + 3]; + DecimalDigits.putPairLatin1(buf, highIntSize + 1, lowInt); + buf[highIntSize] = '.'; + DecimalDigits.getCharsLatin1(highInt, highIntSize, buf); + try { + return JLA.newStringNoRepl(buf, StandardCharsets.ISO_8859_1); + } catch (CharacterCodingException cce) { + throw new AssertionError(cce); + } } - StringBuilderHelper sbHelper = new StringBuilderHelper(); char[] coeff; int offset; // offset is the starting index for coeff array // Get the significand as an absolute value if (intCompact != INFLATED) { - offset = sbHelper.putIntCompact(Math.abs(intCompact)); - coeff = sbHelper.getCompactCharArray(); + coeff = new char[19]; + offset = DecimalDigits.getChars(Math.abs(intCompact), coeff.length, coeff); } else { offset = 0; coeff = intVal.abs().toString().toCharArray(); @@ -4301,7 +4220,7 @@ private String layoutChars(boolean sci) { // If E-notation is needed, length will be: +1 if negative, +1 // if '.' needed, +2 for "E+", + up to 10 for adjusted exponent. // Otherwise it could have +1 if negative, plus leading "0.00000" - StringBuilder buf = sbHelper.getStringBuilder(); + StringBuilder buf = new StringBuilder(32);; if (signum() < 0) // prefix '-' if negative buf.append('-'); int coeffLen = coeff.length - offset; diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java index 0436cbb314f8e..ecfdbd28095d5 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java @@ -487,10 +487,6 @@ public interface JavaLangAccess { */ Object classData(Class c); - int getCharsLatin1(long i, int index, byte[] buf); - - int getCharsUTF16(long i, int index, byte[] buf); - /** * Returns the {@link NativeLibraries} object associated with the provided class loader. * This is used by {@link SymbolLookup#loaderLookup()}. diff --git a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java index 83438e59b8276..75e67e3f9cc90 100644 --- a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java +++ b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java @@ -25,14 +25,18 @@ package jdk.internal.util; +import jdk.internal.misc.Unsafe; import jdk.internal.vm.annotation.Stable; +import static jdk.internal.misc.Unsafe.ARRAY_BYTE_BASE_OFFSET; + /** * Digits class for decimal digits. * * @since 21 */ public final class DecimalDigits { + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); /** * Each element of the array represents the packaging of two ascii characters based on little endian:

@@ -76,15 +80,6 @@ public final class DecimalDigits { private DecimalDigits() { } - /** - * For values from 0 to 99 return a short encoding a pair of ASCII-encoded digit characters in little-endian - * @param i value to convert - * @return a short encoding a pair of ASCII-encoded digit characters - */ - public static short digitPair(int i) { - return DIGITS[i]; - } - /** * Returns the string representation size for a given int value. * @@ -136,4 +131,306 @@ public static int stringSize(long x) { } return 19 + d; } + + /** + * Places characters representing the integer i into the + * character array buf. The characters are placed into + * the buffer backwards starting with the least significant + * digit at the specified index (exclusive), and working + * backwards from there. + * + * @implNote This method converts positive inputs into negative + * values, to cover the Integer.MIN_VALUE case. Converting otherwise + * (negative to positive) will expose -Integer.MIN_VALUE that overflows + * integer. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, Latin1-encoded + * @return index of the most significant digit or minus sign, if present + */ + public static int getCharsLatin1(int i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + int q; + int charPos = index; + + boolean negative = i < 0; + if (!negative) { + i = -i; + } + + // Generate two digits per iteration + while (i <= -100) { + q = i / 100; + charPos -= 2; + putPairLatin1(buf, charPos, (q * 100) - i); + i = q; + } + + // We know there are at most two digits left at this point. + if (i < -9) { + charPos -= 2; + putPairLatin1(buf, charPos, -i); + } else { + putCharLatin1(buf, --charPos, '0' - i); + } + + if (negative) { + putCharLatin1(buf, --charPos, '-'); + } + return charPos; + } + + + /** + * Places characters representing the long i into the + * character array buf. The characters are placed into + * the buffer backwards starting with the least significant + * digit at the specified index (exclusive), and working + * backwards from there. + * + * @implNote This method converts positive inputs into negative + * values, to cover the Long.MIN_VALUE case. Converting otherwise + * (negative to positive) will expose -Long.MIN_VALUE that overflows + * long. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, Latin1-encoded + * @return index of the most significant digit or minus sign, if present + */ + public static int getCharsLatin1(long i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + long q; + int charPos = index; + + boolean negative = (i < 0); + if (!negative) { + i = -i; + } + + // Get 2 digits/iteration using longs until quotient fits into an int + while (i <= Integer.MIN_VALUE) { + q = i / 100; + charPos -= 2; + putPairLatin1(buf, charPos, (int)((q * 100) - i)); + i = q; + } + + // Get 2 digits/iteration using ints + int q2; + int i2 = (int)i; + while (i2 <= -100) { + q2 = i2 / 100; + charPos -= 2; + putPairLatin1(buf, charPos, (q2 * 100) - i2); + i2 = q2; + } + + // We know there are at most two digits left at this point. + if (i2 < -9) { + charPos -= 2; + putPairLatin1(buf, charPos, -i2); + } else { + putCharLatin1(buf, --charPos, '0' - i2); + } + + if (negative) { + putCharLatin1(buf, --charPos, '-'); + } + return charPos; + } + + + /** + * This is a variant of {@link DecimalDigits#getCharsLatin1(int, int, byte[])}, but for + * UTF-16 coder. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, UTF16-coded. + * @return index of the most significant digit or minus sign, if present + */ + public static int getCharsUTF16(int i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + int q, r; + int charPos = index; + + boolean negative = (i < 0); + if (!negative) { + i = -i; + } + + // Get 2 digits/iteration using ints + while (i <= -100) { + q = i / 100; + r = (q * 100) - i; + i = q; + charPos -= 2; + putPairUTF16(buf, charPos, r); + } + + // We know there are at most two digits left at this point. + if (i < -9) { + charPos -= 2; + putPairUTF16(buf, charPos, -i); + } else { + putCharUTF16(buf, --charPos, '0' - i); + } + + if (negative) { + putCharUTF16(buf, --charPos, '-'); + } + return charPos; + } + + + /** + * This is a variant of {@link DecimalDigits#getCharsLatin1(long, int, byte[])}, but for + * UTF-16 coder. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, UTF16-coded. + * @return index of the most significant digit or minus sign, if present + */ + public static int getCharsUTF16(long i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + long q; + int charPos = index; + + boolean negative = (i < 0); + if (!negative) { + i = -i; + } + + // Get 2 digits/iteration using longs until quotient fits into an int + while (i <= Integer.MIN_VALUE) { + q = i / 100; + charPos -= 2; + putPairUTF16(buf, charPos, (int)((q * 100) - i)); + i = q; + } + + // Get 2 digits/iteration using ints + int q2; + int i2 = (int)i; + while (i2 <= -100) { + q2 = i2 / 100; + charPos -= 2; + putPairUTF16(buf, charPos, (q2 * 100) - i2); + i2 = q2; + } + + // We know there are at most two digits left at this point. + if (i2 < -9) { + charPos -= 2; + putPairUTF16(buf, charPos, -i2); + } else { + putCharUTF16(buf, --charPos, '0' - i2); + } + + if (negative) { + putCharUTF16(buf, --charPos, '-'); + } + return charPos; + } + + /** + * This is a variant of {@link DecimalDigits#getCharsUTF16(long, int, byte[])}, but for + * UTF-16 coder. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, UTF16-coded. + * @return index of the most significant digit or minus sign, if present + */ + public static int getChars(long i, int index, char[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + long q; + int charPos = index; + + boolean negative = (i < 0); + if (!negative) { + i = -i; + } + + // Get 2 digits/iteration using longs until quotient fits into an int + while (i <= Integer.MIN_VALUE) { + q = i / 100; + charPos -= 2; + putPair(buf, charPos, (int)((q * 100) - i)); + i = q; + } + + // Get 2 digits/iteration using ints + int q2; + int i2 = (int)i; + while (i2 <= -100) { + q2 = i2 / 100; + charPos -= 2; + putPair(buf, charPos, (q2 * 100) - i2); + i2 = q2; + } + + // We know there are at most two digits left at this point. + if (i2 < -9) { + charPos -= 2; + putPair(buf, charPos, -i2); + } else { + buf[--charPos] = (char) ('0' - i2); + } + + if (negative) { + buf[--charPos] = '-'; + } + return charPos; + } + + /** + * Insert the 2-chars integer into the buf as 2 decimal digit ASCII chars, + * only least significant 16 bits of {@code v} are used. + * @param buf byte buffer to copy into + * @param charPos insert point + * @param v to convert + */ + public static void putPair(char[] buf, int charPos, int v) { + int packed = DIGITS[v]; + buf[charPos ] = (char) (packed & 0xFF); + buf[charPos + 1] = (char) (packed >> 8); + } + + /** + * Insert the 2-bytes integer into the buf as 2 decimal digit ASCII bytes, + * only least significant 16 bits of {@code v} are used. + * @param buf byte buffer to copy into + * @param charPos insert point + * @param v to convert + */ + public static void putPairLatin1(byte[] buf, int charPos, int v) { + int packed = DIGITS[v]; + putCharLatin1(buf, charPos, packed & 0xFF); + putCharLatin1(buf, charPos + 1, packed >> 8); + } + + /** + * Insert the 2-chars integer into the buf as 2 decimal digit UTF16 bytes, + * only least significant 16 bits of {@code v} are used. + * @param buf byte buffer to copy into + * @param charPos insert point + * @param v to convert + */ + public static void putPairUTF16(byte[] buf, int charPos, int v) { + int packed = DIGITS[v]; + putCharUTF16(buf, charPos, packed & 0xFF); + putCharUTF16(buf, charPos + 1, packed >> 8); + } + + private static void putCharLatin1(byte[] buf, int charPos, int c) { + UNSAFE.putByte(buf, ARRAY_BYTE_BASE_OFFSET + charPos, (byte) c); + } + + private static void putCharUTF16(byte[] buf, int charPos, int c) { + UNSAFE.putChar(buf, ARRAY_BYTE_BASE_OFFSET + (charPos << 1), (char) c); + } } diff --git a/src/java.base/share/native/libjli/java.c b/src/java.base/share/native/libjli/java.c index 0bb1daed28a1a..4f9a6426dff20 100644 --- a/src/java.base/share/native/libjli/java.c +++ b/src/java.base/share/native/libjli/java.c @@ -1198,9 +1198,6 @@ ParseArguments(int *pargc, char ***pargv, JLI_StrCmp(arg, "-cp") == 0) { REPORT_ERROR (has_arg_any_len, ARG_ERROR1, arg); SetClassPath(value); - if (mode != LM_SOURCE) { - mode = LM_CLASS; - } } else if (JLI_StrCmp(arg, "--list-modules") == 0) { listModules = JNI_TRUE; } else if (JLI_StrCmp(arg, "--show-resolved-modules") == 0) { @@ -1355,11 +1352,12 @@ ParseArguments(int *pargc, char ***pargv, *pret = 1; } } else if (mode == LM_UNKNOWN) { - /* default to LM_CLASS if -m, -jar and -cp options are - * not specified */ if (!_have_classpath) { SetClassPath("."); } + /* If neither of -m, -jar, --source option is set, then the + * launcher mode is LM_UNKNOWN. In such cases, we determine the + * mode as LM_CLASS or LM_SOURCE per the input file. */ mode = IsSourceFile(arg) ? LM_SOURCE : LM_CLASS; } else if (mode == LM_CLASS && IsSourceFile(arg)) { /* override LM_CLASS mode if given a source file */ diff --git a/src/java.management/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java b/src/java.management/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java index f15c416f29635..fb24055cc8f71 100644 --- a/src/java.management/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java +++ b/src/java.management/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java @@ -162,10 +162,22 @@ public void removeNotificationListener(ObjectName name, connectionId, name, getSubject()); } + // 6238731: set the default domain if no domain is set. + ObjectName nn = name; + if (name.getDomain() == null || name.getDomain().isEmpty()) { + try { + nn = ObjectName.getInstance(mbeanServer.getDefaultDomain(), + name.getKeyPropertyList()); + } catch (MalformedObjectNameException mfoe) { + // impossible, but... + throw new IOException(mfoe.getMessage(), mfoe); + } + } + Exception re = null; for (int i = 0 ; i < listenerIDs.length ; i++) { try { - removeNotificationListener(name, listenerIDs[i]); + removeNotificationListener(nn, listenerIDs[i]); } catch (Exception e) { // Give back the first exception // diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java index 5664c195003c6..466c5d0a14cfe 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 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 @@ -24,29 +24,44 @@ */ package jdk.tools.jlink.internal; +import static jdk.tools.jlink.internal.LinkableRuntimeImage.DIFF_PATTERN; +import static jdk.tools.jlink.internal.LinkableRuntimeImage.RESPATH_PATTERN; + import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; 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.stream.Collectors; import java.util.stream.Stream; import jdk.tools.jlink.internal.Archive.Entry; import jdk.tools.jlink.internal.Archive.Entry.EntryType; +import jdk.tools.jlink.internal.JRTArchive.ResourceFileEntry; import jdk.tools.jlink.internal.ResourcePoolManager.CompressedModuleData; +import jdk.tools.jlink.internal.runtimelink.JimageDiffGenerator; +import jdk.tools.jlink.internal.runtimelink.JimageDiffGenerator.ImageResource; +import jdk.tools.jlink.internal.runtimelink.ResourceDiff; +import jdk.tools.jlink.internal.runtimelink.ResourcePoolReader; +import jdk.tools.jlink.internal.runtimelink.RuntimeImageLinkException; import jdk.tools.jlink.plugin.PluginException; import jdk.tools.jlink.plugin.ResourcePool; +import jdk.tools.jlink.plugin.ResourcePoolBuilder; import jdk.tools.jlink.plugin.ResourcePoolEntry; +import jdk.tools.jlink.plugin.ResourcePoolModule; /** * An image (native endian.) @@ -68,38 +83,61 @@ * } */ public final class ImageFileCreator { + private static final byte[] EMPTY_RESOURCE_BYTES = new byte[] {}; + + private static final String JLINK_MOD_NAME = "jdk.jlink"; + private static final String RESPATH = "/" + JLINK_MOD_NAME + "/" + RESPATH_PATTERN; + private static final String DIFF_PATH = "/" + JLINK_MOD_NAME + "/" + DIFF_PATTERN; private final Map> entriesForModule = new HashMap<>(); private final ImagePluginStack plugins; - private ImageFileCreator(ImagePluginStack plugins) { - this.plugins = Objects.requireNonNull(plugins); - } - - public static ExecutableImage create(Set archives, - ImagePluginStack plugins) - throws IOException { - return ImageFileCreator.create(archives, ByteOrder.nativeOrder(), - plugins); - } + private final boolean generateRuntimeImage; + private final TaskHelper helper; - public static ExecutableImage create(Set archives, - ByteOrder byteOrder) - throws IOException { - return ImageFileCreator.create(archives, byteOrder, - new ImagePluginStack()); + private ImageFileCreator(ImagePluginStack plugins, + boolean generateRuntimeImage, + TaskHelper taskHelper) { + this.plugins = Objects.requireNonNull(plugins); + this.generateRuntimeImage = generateRuntimeImage; + this.helper = taskHelper; } + /** + * Create an executable image based on a set of input archives and a given + * plugin stack for a given byte order. It optionally generates a runtime + * that can be used for linking from the run-time image if + * {@code generateRuntimeImage} is set to {@code true}. + * + * @param archives The set of input archives + * @param byteOrder The desired byte order of the result + * @param plugins The plugin stack to apply to the input + * @param generateRuntimeImage if a runtime suitable for linking from the + * run-time image should get created. + * @return The executable image. + * @throws IOException + */ public static ExecutableImage create(Set archives, ByteOrder byteOrder, - ImagePluginStack plugins) + ImagePluginStack plugins, + boolean generateRuntimeImage, + TaskHelper taskHelper) throws IOException { - ImageFileCreator image = new ImageFileCreator(plugins); + ImageFileCreator image = new ImageFileCreator(plugins, + generateRuntimeImage, + taskHelper); try { image.readAllEntries(archives); // write to modular image image.writeImage(archives, byteOrder); + } catch (RuntimeImageLinkException e) { + // readAllEntries() might throw this exception. + // Propagate as IOException with appropriate message for + // jlink runs from the run-time image. This handles better + // error messages for the case of modified files in the run-time + // image. + throw image.newIOException(e); } finally { - //Close all archives + // Close all archives for (Archive a : archives) { a.close(); } @@ -125,7 +163,8 @@ private void readAllEntries(Set archives) { public static void recreateJimage(Path jimageFile, Set archives, - ImagePluginStack pluginSupport) + ImagePluginStack pluginSupport, + boolean generateRuntimeImage) throws IOException { try { Map> entriesForModule @@ -142,7 +181,7 @@ public static void recreateJimage(Path jimageFile, try (OutputStream fos = Files.newOutputStream(jimageFile); BufferedOutputStream bos = new BufferedOutputStream(fos); DataOutputStream out = new DataOutputStream(bos)) { - generateJImage(pool, writer, pluginSupport, out); + generateJImage(pool, writer, pluginSupport, out, generateRuntimeImage); } } finally { //Close all archives @@ -158,9 +197,14 @@ private void writeImage(Set archives, BasicImageWriter writer = new BasicImageWriter(byteOrder); ResourcePoolManager allContent = createPoolManager(archives, entriesForModule, byteOrder, writer); - ResourcePool result; + ResourcePool result = null; try (DataOutputStream out = plugins.getJImageFileOutputStream()) { - result = generateJImage(allContent, writer, plugins, out); + result = generateJImage(allContent, writer, plugins, out, generateRuntimeImage); + } catch (RuntimeImageLinkException e) { + // Propagate as IOException with appropriate message for + // jlink runs from the run-time image. This handles better + // error messages for the case of --patch-module. + throw newIOException(e); } //Handle files. @@ -174,14 +218,53 @@ private void writeImage(Set archives, } } + private IOException newIOException(RuntimeImageLinkException e) throws IOException { + if (JlinkTask.DEBUG) { + e.printStackTrace(); + } + String message = switch (e.getReason()) { + case PATCH_MODULE -> helper.getMessage("err.runtime.link.patched.module", e.getFile()); + case MODIFIED_FILE -> helper.getMessage("err.runtime.link.modified.file", e.getFile()); + default -> throw new AssertionError("Unexpected value: " + e.getReason()); + }; + throw new IOException(message); + } + + /** + * Create a jimage based on content of the given ResourcePoolManager, + * optionally creating a runtime that can be used for linking from the + * run-time image + * + * @param allContent The content that needs to get added to the resulting + * lib/modules (jimage) file. + * @param writer The writer for the jimage file. + * @param pluginSupport The stack of all plugins to apply. + * @param out The output stream to write the jimage to. + * @param generateRuntimeImage if a runtime suitable for linking from the + * run-time image should get created. + * @return A pool of the actual result resources. + * @throws IOException + */ private static ResourcePool generateJImage(ResourcePoolManager allContent, BasicImageWriter writer, ImagePluginStack pluginSupport, - DataOutputStream out + DataOutputStream out, + boolean generateRuntimeImage ) throws IOException { ResourcePool resultResources; try { resultResources = pluginSupport.visitResources(allContent); + if (generateRuntimeImage) { + // Keep track of non-modules resources for linking from a run-time image + resultResources = addNonClassResourcesTrackFiles(resultResources, + writer); + // Generate the diff between the input resources from packaged + // modules in 'allContent' to the plugin- or otherwise + // generated-content in 'resultResources' + resultResources = addResourceDiffFiles(allContent.resourcePool(), + resultResources, + writer); + } } catch (PluginException pe) { if (JlinkTask.DEBUG) { pe.printStackTrace(); @@ -198,7 +281,7 @@ private static ResourcePool generateJImage(ResourcePoolManager allContent, List content = new ArrayList<>(); List paths = new ArrayList<>(); - // the order of traversing the resources and the order of + // the order of traversing the resources and the order of // the module content being written must be the same resultResources.entries().forEach(res -> { if (res.type().equals(ResourcePoolEntry.Type.CLASS_OR_RESOURCE)) { @@ -248,11 +331,225 @@ private static ResourcePool generateJImage(ResourcePoolManager allContent, return resultResources; } + /** + * Support for creating a runtime suitable for linking from the run-time + * image. + * + * Generates differences between the packaged modules "view" in + * {@code jmodContent} to the optimized image in {@code resultContent} and + * adds the result to the returned resource pool. + * + * @param jmodContent The resource pool view of packaged modules + * @param resultContent The optimized result generated from the jmodContent + * input by applying the plugin stack. + * @param writer The image writer. + * @return The resource pool with the difference file resources added to + * the {@code resultContent} + */ + @SuppressWarnings("try") + private static ResourcePool addResourceDiffFiles(ResourcePool jmodContent, + ResourcePool resultContent, + BasicImageWriter writer) { + JimageDiffGenerator generator = new JimageDiffGenerator(); + List diff; + try (ImageResource jmods = new ResourcePoolReader(jmodContent); + ImageResource jimage = new ResourcePoolReader(resultContent)) { + diff = generator.generateDiff(jmods, jimage); + } catch (Exception e) { + throw new AssertionError("Failed to generate the runtime image diff", e); + } + Set modules = resultContent.moduleView().modules() + .map(a -> a.name()) + .collect(Collectors.toSet()); + // Add resource diffs for the resource files we are about to add + modules.stream().forEach(m -> { + String resourceName = String.format(DIFF_PATH, m); + ResourceDiff.Builder builder = new ResourceDiff.Builder(); + ResourceDiff d = builder.setKind(ResourceDiff.Kind.ADDED) + .setName(resourceName) + .build(); + diff.add(d); + }); + Map> perModDiffs = preparePerModuleDiffs(diff, + modules); + return addDiffResourcesFiles(modules, perModDiffs, resultContent, writer); + } + + private static Map> preparePerModuleDiffs(List resDiffs, + Set modules) { + Map> modToDiff = new HashMap<>(); + resDiffs.forEach(d -> { + int secondSlash = d.getName().indexOf("/", 1); + if (secondSlash == -1) { + throw new AssertionError("Module name not present"); + } + String module = d.getName().substring(1, secondSlash); + List perModDiff = modToDiff.computeIfAbsent(module, + a -> new ArrayList<>()); + perModDiff.add(d); + }); + Map> allModsToDiff = new HashMap<>(); + modules.stream().forEach(m -> { + List d = modToDiff.get(m); + if (d == null) { + // Not all modules will have a diff + allModsToDiff.put(m, Collections.emptyList()); + } else { + allModsToDiff.put(m, d); + } + }); + return allModsToDiff; + } + + private static ResourcePool addDiffResourcesFiles(Set modules, + Map> perModDiffs, + ResourcePool resultResources, + BasicImageWriter writer) { + ResourcePoolManager mgr = createPoolManager(resultResources, writer); + ResourcePoolBuilder out = mgr.resourcePoolBuilder(); + modules.stream().sorted().forEach(module -> { + String mResource = String.format(DIFF_PATH, module); + List diff = perModDiffs.get(module); + // Note that for modules without diff to the packaged modules view + // we create resource diff files with just the header and no content. + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + try { + ResourceDiff.write(diff, bout); + } catch (IOException e) { + throw new AssertionError("Failed to write resource diff file" + + " for module " + module, e); + } + out.add(ResourcePoolEntry.create(mResource, bout.toByteArray())); + }); + return out.build(); + } + + /** + * Support for creating runtimes that can be used for linking from the + * run-time image. Adds meta-data files for resources not in the lib/modules + * file of the JDK. That is, mapping files for which on-disk files belong to + * which module. + * + * @param resultResources + * The original resources which serve as the basis for generating + * the meta-data files. + * @param writer + * The image writer. + * + * @return An amended resource pool which includes meta-data files. + */ + private static ResourcePool addNonClassResourcesTrackFiles(ResourcePool resultResources, + BasicImageWriter writer) { + // Only add resources if jdk.jlink module is present in the target image + Optional jdkJlink = resultResources.moduleView() + .findModule(JLINK_MOD_NAME); + if (jdkJlink.isPresent()) { + Map> nonClassResources = recordAndFilterEntries(resultResources); + return addModuleResourceEntries(resultResources, nonClassResources, writer); + } else { + return resultResources; // No-op + } + } + + /** + * Support for creating runtimes that can be used for linking from the + * run-time image. Adds the given mapping of files as a meta-data file to + * the given resource pool. + * + * @param resultResources + * The resource pool to add files to. + * @param nonClassResEntries + * The per module mapping for which to create the meta-data files + * for. + * @param writer + * The image writer. + * + * @return A resource pool with meta-data files added. + */ + private static ResourcePool addModuleResourceEntries(ResourcePool resultResources, + Map> nonClassResEntries, + BasicImageWriter writer) { + Set inputModules = resultResources.moduleView().modules() + .map(rm -> rm.name()) + .collect(Collectors.toSet()); + ResourcePoolManager mgr = createPoolManager(resultResources, writer); + ResourcePoolBuilder out = mgr.resourcePoolBuilder(); + inputModules.stream().sorted().forEach(module -> { + String mResource = String.format(RESPATH, module); + List mResources = nonClassResEntries.get(module); + if (mResources == null) { + // We create empty resource files for modules in the resource + // pool view that don't themselves contain native resources + // or config files. + out.add(ResourcePoolEntry.create(mResource, EMPTY_RESOURCE_BYTES)); + } else { + String mResContent = mResources.stream().sorted() + .collect(Collectors.joining("\n")); + out.add(ResourcePoolEntry.create(mResource, + mResContent.getBytes(StandardCharsets.UTF_8))); + } + }); + return out.build(); + } + + /** + * Support for creating runtimes that can be used for linking from the + * run-time image. Generates a per module mapping of files not part of the + * modules image (jimage). This mapping is needed so as to know which files + * of the installed JDK belong to which module. + * + * @param resultResources + * The resources from which the mapping gets generated + * @return A mapping with the module names as keys and the list of files not + * part of the modules image (jimage) as values. + */ + private static Map> recordAndFilterEntries(ResourcePool resultResources) { + Map> nonClassResEntries = new HashMap<>(); + Platform platform = getTargetPlatform(resultResources); + resultResources.entries().forEach(entry -> { + // Note that the fs_$module_files file is a resource file itself, so + // we cannot add fs_$module_files themselves due to the + // not(class_or_resources) condition. However, we also don't want + // to track 'release' file entries (not(top) condition) as those are + // handled by the release info plugin. + if (entry.type() != ResourcePoolEntry.Type.CLASS_OR_RESOURCE && + entry.type() != ResourcePoolEntry.Type.TOP) { + List mRes = nonClassResEntries.computeIfAbsent(entry.moduleName(), + a -> new ArrayList<>()); + ResourceFileEntry rfEntry = ResourceFileEntry.toResourceFileEntry(entry, + platform); + mRes.add(rfEntry.encodeToString()); + } + }); + return nonClassResEntries; + } + + private static Platform getTargetPlatform(ResourcePool in) { + String platform = in.moduleView().findModule("java.base") + .map(ResourcePoolModule::targetPlatform) + .orElseThrow(() -> new AssertionError("java.base not found")); + return Platform.parsePlatform(platform); + } + private static ResourcePoolManager createPoolManager(Set archives, Map> entriesForModule, ByteOrder byteOrder, BasicImageWriter writer) throws IOException { - ResourcePoolManager resources = new ResourcePoolManager(byteOrder, new StringTable() { + ResourcePoolManager resources = createBasicResourcePoolManager(byteOrder, writer); + archives.stream() + .map(Archive::moduleName) + .sorted() + .flatMap(mn -> + entriesForModule.get(mn).stream() + .map(e -> new ArchiveEntryResourcePoolEntry(mn, + e.getResourcePoolEntryName(), e))) + .forEach(resources::add); + return resources; + } + + private static ResourcePoolManager createBasicResourcePoolManager(ByteOrder byteOrder, + BasicImageWriter writer) { + return new ResourcePoolManager(byteOrder, new StringTable() { @Override public int addString(String str) { @@ -264,14 +561,25 @@ public String getString(int id) { return writer.getString(id); } }); - archives.stream() - .map(Archive::moduleName) - .sorted() - .flatMap(mn -> - entriesForModule.get(mn).stream() - .map(e -> new ArchiveEntryResourcePoolEntry(mn, - e.getResourcePoolEntryName(), e))) - .forEach(resources::add); + } + + /** + * Creates a ResourcePoolManager from existing resources so that more + * resources can be appended. + * + * @param resultResources The existing resources to initially add. + * @param writer The basic image writer. + * @return An appendable ResourcePoolManager. + */ + private static ResourcePoolManager createPoolManager(ResourcePool resultResources, + BasicImageWriter writer) { + ResourcePoolManager resources = createBasicResourcePoolManager(resultResources.byteOrder(), + writer); + // Note that resources are already sorted in the correct order. + // The underlying ResourcePoolManager keeps track of entries via + // LinkedHashMap, which keeps values in insertion order. Therefore + // adding resources here, preserving that same order is OK. + resultResources.entries().forEach(resources::add); return resources; } diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java new file mode 100644 index 0000000000000..755afea8c60b3 --- /dev/null +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java @@ -0,0 +1,525 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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.tools.jlink.internal; + +import static jdk.tools.jlink.internal.LinkableRuntimeImage.RESPATH_PATTERN; +import static jdk.tools.jlink.internal.runtimelink.RuntimeImageLinkException.Reason.MODIFIED_FILE; +import static jdk.tools.jlink.internal.runtimelink.RuntimeImageLinkException.Reason.PATCH_MODULE; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.lang.module.ModuleFinder; +import java.lang.module.ModuleReference; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HexFormat; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import jdk.internal.util.OperatingSystem; +import jdk.tools.jlink.internal.Archive.Entry.EntryType; +import jdk.tools.jlink.internal.runtimelink.ResourceDiff; +import jdk.tools.jlink.internal.runtimelink.RuntimeImageLinkException; +import jdk.tools.jlink.plugin.ResourcePoolEntry; +import jdk.tools.jlink.plugin.ResourcePoolEntry.Type; + +/** + * An archive implementation based on the JDK's run-time image. That is, classes + * and resources from the modules image (lib/modules, or jimage) and other + * associated files from the filesystem of the JDK installation. + */ +public class JRTArchive implements Archive { + + private final String module; + private final Path path; + private final ModuleReference ref; + // The collection of files of this module + private final List files = new ArrayList<>(); + // Files not part of the lib/modules image of the JDK install. + // Thus, native libraries, binaries, legal files, etc. + private final List otherRes; + // Maps a module resource path to the corresponding diff to packaged + // modules for that resource (if any) + private final Map resDiff; + private final boolean errorOnModifiedFile; + private final TaskHelper taskHelper; + + /** + * JRTArchive constructor + * + * @param module The module name this archive refers to + * @param path The JRT filesystem path. + * @param errorOnModifiedFile Whether or not modified files of the JDK + * install aborts the link. + * @param perModDiff The lib/modules (a.k.a jimage) diff for this module, + * possibly an empty list if there are no differences. + */ + JRTArchive(String module, + Path path, + boolean errorOnModifiedFile, + List perModDiff, + TaskHelper taskHelper) { + this.module = module; + this.path = path; + this.ref = ModuleFinder.ofSystem() + .find(module) + .orElseThrow(() -> + new IllegalArgumentException( + "Module " + module + + " not part of the JDK install")); + this.errorOnModifiedFile = errorOnModifiedFile; + this.otherRes = readModuleResourceFile(module); + this.resDiff = Objects.requireNonNull(perModDiff).stream() + .collect(Collectors.toMap(ResourceDiff::getName, Function.identity())); + this.taskHelper = taskHelper; + } + + @Override + public String moduleName() { + return module; + } + + @Override + public Path getPath() { + return path; + } + + @Override + public Stream entries() { + try { + collectFiles(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return files.stream().map(JRTFile::toEntry); + } + + @Override + public void open() throws IOException { + if (files.isEmpty()) { + collectFiles(); + } + } + + @Override + public void close() throws IOException { + if (!files.isEmpty()) { + files.clear(); + } + } + + @Override + public int hashCode() { + return Objects.hash(module, path); + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof JRTArchive other && + Objects.equals(module, other.module) && + Objects.equals(path, other.path)); + } + + private void collectFiles() throws IOException { + if (files.isEmpty()) { + addNonClassResources(); + // Add classes/resources from the run-time image, + // patched with the run-time image diff + files.addAll(ref.open().list() + .filter(i -> { + String lookupKey = String.format("/%s/%s", module, i); + ResourceDiff rd = resDiff.get(lookupKey); + // Filter all resources with a resource diff + // that are of kind MODIFIED. + // Note that REMOVED won't happen since in + // that case the module listing won't have + // the resource anyway. + // Note as well that filter removes files + // of kind ADDED. Those files are not in + // the packaged modules, so ought not to + // get returned from the pipeline. + return (rd == null || + rd.getKind() == ResourceDiff.Kind.MODIFIED); + }) + .map(s -> { + String lookupKey = String.format("/%s/%s", module, s); + return new JRTArchiveFile(JRTArchive.this, s, + EntryType.CLASS_OR_RESOURCE, + null /* hashOrTarget */, + false /* symlink */, + resDiff.get(lookupKey)); + }) + .toList()); + // Finally add all files only present in the resource diff + // That is, removed items in the run-time image. + files.addAll(resDiff.values().stream() + .filter(rd -> rd.getKind() == ResourceDiff.Kind.REMOVED) + .map(s -> { + int secondSlash = s.getName().indexOf("/", 1); + assert secondSlash != -1; + String pathWithoutModule = s.getName().substring(secondSlash + 1); + return new JRTArchiveFile(JRTArchive.this, + pathWithoutModule, + EntryType.CLASS_OR_RESOURCE, + null /* hashOrTarget */, + false /* symlink */, + s); + }) + .toList()); + } + } + + /* + * no need to keep track of the warning produced since this is eagerly + * checked once. + */ + private void addNonClassResources() { + // Not all modules will have other resources like bin, lib, legal etc. + // files. In that case the list will be empty. + if (!otherRes.isEmpty()) { + files.addAll(otherRes.stream() + .filter(Predicate.not(String::isEmpty)) + .map(s -> { + ResourceFileEntry m = ResourceFileEntry.decodeFromString(s); + + // Read from the base JDK image. + Path path = BASE.resolve(m.resPath); + if (shaSumMismatch(path, m.hashOrTarget, m.symlink)) { + if (errorOnModifiedFile) { + throw new RuntimeImageLinkException(path.toString(), MODIFIED_FILE); + } else { + taskHelper.warning("err.runtime.link.modified.file", path.toString()); + } + } + + return new JRTArchiveFile(JRTArchive.this, + m.resPath, + toEntryType(m.resType), + m.hashOrTarget, + m.symlink, + /* diff only for resources */ + null); + }) + .toList()); + } + } + + static boolean shaSumMismatch(Path res, String expectedSha, boolean isSymlink) { + if (isSymlink) { + return false; + } + // handle non-symlink resources + try { + HexFormat format = HexFormat.of(); + byte[] expected = format.parseHex(expectedSha); + MessageDigest digest = MessageDigest.getInstance("SHA-512"); + try (InputStream is = Files.newInputStream(res)) { + byte[] buf = new byte[1024]; + int readBytes = -1; + while ((readBytes = is.read(buf)) != -1) { + digest.update(buf, 0, readBytes); + } + } + byte[] actual = digest.digest(); + return !MessageDigest.isEqual(expected, actual); + } catch (Exception e) { + throw new AssertionError("SHA-512 sum check failed!", e); + } + } + + private static EntryType toEntryType(Type input) { + return switch(input) { + case CLASS_OR_RESOURCE -> EntryType.CLASS_OR_RESOURCE; + case CONFIG -> EntryType.CONFIG; + case HEADER_FILE -> EntryType.HEADER_FILE; + case LEGAL_NOTICE -> EntryType.LEGAL_NOTICE; + case MAN_PAGE -> EntryType.MAN_PAGE; + case NATIVE_CMD -> EntryType.NATIVE_CMD; + case NATIVE_LIB -> EntryType.NATIVE_LIB; + case TOP -> throw new IllegalArgumentException( + "TOP files should be handled by ReleaseInfoPlugin!"); + default -> throw new IllegalArgumentException("Unknown type: " + input); + }; + } + + public record ResourceFileEntry(Type resType, + boolean symlink, + String hashOrTarget, + String resPath) { + // Type file format: + // '|{0,1}||' + // (1) (2) (3) (4) + // + // Where fields are: + // + // (1) The resource type as specified by ResourcePoolEntry.type() + // (2) Symlink designator. 0 => regular resource, 1 => symlinked resource + // (3) The SHA-512 sum of the resources' content. The link to the target + // for symlinked resources. + // (4) The relative file path of the resource + private static final String TYPE_FILE_FORMAT = "%d|%d|%s|%s"; + + private static final Map typeMap = Arrays.stream(Type.values()) + .collect(Collectors.toMap(Type::ordinal, Function.identity())); + + public String encodeToString() { + return String.format(TYPE_FILE_FORMAT, + resType.ordinal(), + symlink ? 1 : 0, + hashOrTarget, + resPath); + } + + /** + * line: ||| + * + * Take the integer before '|' convert it to a Type. The second + * token is an integer representing symlinks (or not). The third token is + * a hash sum (sha512) of the file denoted by the fourth token (path). + */ + static ResourceFileEntry decodeFromString(String line) { + assert !line.isEmpty(); + + String[] tokens = line.split("\\|", 4); + Type type = null; + int symlinkNum = -1; + try { + Integer typeInt = Integer.valueOf(tokens[0]); + type = typeMap.get(typeInt); + if (type == null) { + throw new AssertionError("Illegal type ordinal: " + typeInt); + } + symlinkNum = Integer.valueOf(tokens[1]); + } catch (NumberFormatException e) { + throw new AssertionError(e); // must not happen + } + if (symlinkNum < 0 || symlinkNum > 1) { + throw new AssertionError( + "Symlink designator out of range [0,1] got: " + + symlinkNum); + } + return new ResourceFileEntry(type, + symlinkNum == 1, + tokens[2] /* hash or target */, + tokens[3] /* resource path */); + } + + public static ResourceFileEntry toResourceFileEntry(ResourcePoolEntry entry, + Platform platform) { + String resPathWithoutMod = dropModuleFromPath(entry, platform); + // Symlinks don't have a hash sum, but a link to the target instead + String hashOrTarget = entry.linkedTarget() == null + ? computeSha512(entry) + : dropModuleFromPath(entry.linkedTarget(), + platform); + return new ResourceFileEntry(entry.type(), + entry.linkedTarget() != null, + hashOrTarget, + resPathWithoutMod); + } + + private static String computeSha512(ResourcePoolEntry entry) { + try { + assert entry.linkedTarget() == null; + MessageDigest digest = MessageDigest.getInstance("SHA-512"); + try (InputStream is = entry.content()) { + byte[] buf = new byte[1024]; + int bytesRead = -1; + while ((bytesRead = is.read(buf)) != -1) { + digest.update(buf, 0, bytesRead); + } + } + byte[] db = digest.digest(); + HexFormat format = HexFormat.of(); + return format.formatHex(db); + } catch (Exception e) { + throw new AssertionError("Failed to generate hash sum for " + + entry.path()); + } + } + + private static String dropModuleFromPath(ResourcePoolEntry entry, + Platform platform) { + String resPath = entry.path() + .substring( + // + 2 => prefixed and suffixed '/' + // For example: '/java.base/' + entry.moduleName().length() + 2); + if (!isWindows(platform)) { + return resPath; + } + // For Windows the libraries live in the 'bin' folder rather than + // the 'lib' folder in the final image. Note that going by the + // NATIVE_LIB type only is insufficient since only files with suffix + // .dll/diz/map/pdb are transplanted to 'bin'. + // See: DefaultImageBuilder.nativeDir() + return nativeDir(entry, resPath); + } + + private static boolean isWindows(Platform platform) { + return platform.os() == OperatingSystem.WINDOWS; + } + + private static String nativeDir(ResourcePoolEntry entry, String resPath) { + if (entry.type() != ResourcePoolEntry.Type.NATIVE_LIB) { + return resPath; + } + // precondition: Native lib, windows platform + if (resPath.endsWith(".dll") || resPath.endsWith(".diz") + || resPath.endsWith(".pdb") || resPath.endsWith(".map")) { + if (resPath.startsWith(LIB_DIRNAME + "/")) { + return BIN_DIRNAME + "/" + + resPath.substring((LIB_DIRNAME + "/").length()); + } + } + return resPath; + } + private static final String BIN_DIRNAME = "bin"; + private static final String LIB_DIRNAME = "lib"; + } + + private static final Path BASE = Paths.get(System.getProperty("java.home")); + + interface JRTFile { + Entry toEntry(); + } + + record JRTArchiveFile(Archive archive, + String resPath, + EntryType resType, + String sha, + boolean symlink, + ResourceDiff diff) implements JRTFile { + public Entry toEntry() { + return new Entry(archive, + String.format("/%s/%s", + archive.moduleName(), + resPath), + resPath, + resType) { + @Override + public long size() { + try { + if (resType != EntryType.CLASS_OR_RESOURCE) { + // Read from the base JDK image, special casing + // symlinks, which have the link target in the + // hashOrTarget field + if (symlink) { + return Files.size(BASE.resolve(sha)); + } + return Files.size(BASE.resolve(resPath)); + } else { + if (diff != null) { + // If the resource has a diff to the + // packaged modules, use the diff. Diffs of kind + // ADDED have been filtered out in collectFiles(); + assert diff.getKind() != ResourceDiff.Kind.ADDED; + assert diff.getName().equals(String.format("/%s/%s", + archive.moduleName(), + resPath)); + return diff.getResourceBytes().length; + } + // Read from the module image. This works, because + // the underlying base path is a JrtPath with the + // JrtFileSystem underneath which is able to handle + // this size query. + try { + return Files.size(archive.getPath().resolve(resPath)); + } catch (NoSuchFileException file) { + // This indicates that we don't find the class in the + // modules image using the JRT FS provider. Yet, we find + // the class using the system module finder. Therefore, + // we have a patched module. Mention that module patching + // is not supported. + throw new RuntimeImageLinkException(file.getFile(), PATCH_MODULE); + } + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public InputStream stream() throws IOException { + if (resType != EntryType.CLASS_OR_RESOURCE) { + // Read from the base JDK image. + Path path = symlink ? BASE.resolve(sha) : BASE.resolve(resPath); + return Files.newInputStream(path); + } else { + // Read from the module image. Use the diff to the + // packaged modules if we have one. Diffs of kind + // ADDED have been filtered out in collectFiles(); + if (diff != null) { + assert diff.getKind() != ResourceDiff.Kind.ADDED; + assert diff.getName().equals(String.format("/%s/%s", + archive.moduleName(), + resPath)); + return new ByteArrayInputStream(diff.getResourceBytes()); + } + String module = archive.moduleName(); + ModuleReference mRef = ModuleFinder.ofSystem() + .find(module).orElseThrow(); + return mRef.open().open(resPath).orElseThrow(); + } + } + + }; + } + } + + private static List readModuleResourceFile(String modName) { + String resName = String.format(RESPATH_PATTERN, modName); + try { + try (InputStream inStream = JRTArchive.class.getModule() + .getResourceAsStream(resName)) { + String input = new String(inStream.readAllBytes(), StandardCharsets.UTF_8); + if (input.isEmpty()) { + // Not all modules have non-class resources + return Collections.emptyList(); + } else { + return Arrays.asList(input.split("\n")); + } + } + } catch (IOException e) { + throw new UncheckedIOException("Failed to process resources from the " + + "run-time image for module " + modName, e); + } + } +} diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Jlink.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Jlink.java index 1c5b0a3cd57c3..465a1cae8d98b 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Jlink.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Jlink.java @@ -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 @@ -26,7 +26,6 @@ import java.lang.module.Configuration; import java.lang.module.ModuleFinder; -import java.nio.ByteOrder; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; @@ -148,6 +147,9 @@ public static final class JlinkConfiguration { private final Path output; private final Set modules; private final ModuleFinder finder; + private final boolean linkFromRuntimeImage; + private final boolean ignoreModifiedRuntime; + private final boolean generateRuntimeImage; /** * jlink configuration, @@ -158,10 +160,16 @@ public static final class JlinkConfiguration { */ public JlinkConfiguration(Path output, Set modules, - ModuleFinder finder) { + ModuleFinder finder, + boolean linkFromRuntimeImage, + boolean ignoreModifiedRuntime, + boolean generateRuntimeImage) { this.output = output; this.modules = Objects.requireNonNull(modules); this.finder = finder; + this.linkFromRuntimeImage = linkFromRuntimeImage; + this.ignoreModifiedRuntime = ignoreModifiedRuntime; + this.generateRuntimeImage = generateRuntimeImage; } /** @@ -186,6 +194,18 @@ public ModuleFinder finder() { return finder; } + public boolean linkFromRuntimeImage() { + return linkFromRuntimeImage; + } + + public boolean ignoreModifiedRuntime() { + return ignoreModifiedRuntime; + } + + public boolean isGenerateRuntimeImage() { + return generateRuntimeImage; + } + /** * Returns a {@link Configuration} of the given module path, * root modules with full service binding. diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java index d9dbf1d06613f..15998d6b92959 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java @@ -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 @@ -24,6 +24,8 @@ */ package jdk.tools.jlink.internal; +import static jdk.tools.jlink.internal.TaskHelper.JLINK_BUNDLE; + import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; @@ -39,14 +41,15 @@ import java.lang.module.ResolvedModule; import java.net.URI; import java.nio.ByteOrder; -import java.nio.file.Files; import java.nio.file.FileVisitResult; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; @@ -60,18 +63,18 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import jdk.internal.module.ModulePath; import jdk.internal.module.ModuleReferenceImpl; -import jdk.tools.jlink.internal.TaskHelper.BadArgs; -import static jdk.tools.jlink.internal.TaskHelper.JLINK_BUNDLE; +import jdk.internal.module.ModuleResolution; +import jdk.internal.opt.CommandLine; +import jdk.tools.jlink.internal.ImagePluginStack.ImageProvider; import jdk.tools.jlink.internal.Jlink.JlinkConfiguration; import jdk.tools.jlink.internal.Jlink.PluginsConfiguration; +import jdk.tools.jlink.internal.TaskHelper.BadArgs; import jdk.tools.jlink.internal.TaskHelper.Option; import jdk.tools.jlink.internal.TaskHelper.OptionsHelper; -import jdk.tools.jlink.internal.ImagePluginStack.ImageProvider; +import jdk.tools.jlink.internal.runtimelink.RuntimeImageLinkException; import jdk.tools.jlink.plugin.PluginException; -import jdk.internal.opt.CommandLine; -import jdk.internal.module.ModulePath; -import jdk.internal.module.ModuleResolution; /** * Implementation for the jlink tool. @@ -86,7 +89,6 @@ public class JlinkTask { private static final TaskHelper taskHelper = new TaskHelper(JLINK_BUNDLE); - private static final Option[] recognizedOptions = { new Option(false, (task, opt, arg) -> { task.options.help = true; @@ -182,7 +184,17 @@ public class JlinkTask { }, true, "--full-version"), new Option(false, (task, opt, arg) -> { task.options.ignoreSigning = true; - }, "--ignore-signing-information"),}; + }, "--ignore-signing-information"), + new Option(false, (task, opt, arg) -> { + task.options.ignoreModifiedRuntime = true; + }, true, "--ignore-modified-runtime"), + // option for generating a runtime that can then + // be used for linking from the run-time image. + new Option(false, (task, opt, arg) -> { + task.options.generateLinkableRuntime = true; + }, true, "--generate-linkable-runtime") + }; + private static final String PROGNAME = "jlink"; private final OptionsValues options = new OptionsValues(); @@ -222,6 +234,8 @@ static class OptionsValues { boolean ignoreSigning = false; boolean bindServices = false; boolean suggestProviders = false; + boolean ignoreModifiedRuntime = false; + boolean generateLinkableRuntime = false; } public static final String OPTIONS_RESOURCE = "jdk/tools/jlink/internal/options"; @@ -252,7 +266,7 @@ int run(String[] args) { .showUsage(true); } if (options.help) { - optionsHelper.showHelp(PROGNAME); + optionsHelper.showHelp(PROGNAME, LinkableRuntimeImage.isLinkableRuntime()); return EXIT_OK; } if (optionsHelper.shouldListPlugins()) { @@ -270,11 +284,6 @@ int run(String[] args) { if (jmods != null) { options.modulePath.add(jmods); } - - if (options.modulePath.isEmpty()) { - throw taskHelper.newBadArgs("err.modulepath.must.be.specified") - .showUsage(true); - } } JlinkConfiguration config = initJlinkConfig(); @@ -300,7 +309,7 @@ int run(String[] args) { } cleanupOutput(outputPath); return EXIT_ERROR; - } catch (IllegalArgumentException | ResolutionException e) { + } catch (IllegalArgumentException | ResolutionException | RuntimeImageLinkException e) { log.println(taskHelper.getMessage("error.prefix") + " " + e.getMessage()); if (DEBUG) { e.printStackTrace(log); @@ -356,6 +365,7 @@ public static void createImage(JlinkConfiguration config, false, null, false, + new OptionsValues(), null); // Then create the Plugin Stack @@ -370,7 +380,7 @@ public static void createImage(JlinkConfiguration config, private JlinkConfiguration initJlinkConfig() throws BadArgs { Set roots = new HashSet<>(); for (String mod : options.addMods) { - if (mod.equals(ALL_MODULE_PATH)) { + if (mod.equals(ALL_MODULE_PATH) && options.modulePath.size() > 0) { ModuleFinder finder = newModuleFinder(options.modulePath, options.limitMods, Set.of()); // all observable modules are roots finder.findAll() @@ -392,9 +402,75 @@ private JlinkConfiguration initJlinkConfig() throws BadArgs { finder = newModuleFinder(options.modulePath, options.limitMods, roots); } + boolean isLinkFromRuntime = options.modulePath.isEmpty(); + // In case of custom modules outside the JDK we may + // have a non-empty module path, which must not include + // java.base. If it did, we link using packaged modules from that + // module path. If the module path does not include java.base, we have + // the case where we link from the run-time image. In that case, we take + // the JDK modules from the run-time image (ModuleFinder.ofSystem()). + if (finder.find("java.base").isEmpty()) { + isLinkFromRuntime = true; + ModuleFinder runtimeImageFinder = ModuleFinder.ofSystem(); + finder = combinedFinders(runtimeImageFinder, finder, options.limitMods, roots); + } + + // --keep-packaged-modules doesn't make sense as we are not linking + // from packaged modules to begin with. + if (isLinkFromRuntime && options.packagedModulesPath != null) { + throw taskHelper.newBadArgs("err.runtime.link.packaged.mods"); + } + return new JlinkConfiguration(options.output, roots, - finder); + finder, + isLinkFromRuntime, + options.ignoreModifiedRuntime, + options.generateLinkableRuntime); + } + + /** + * Creates a combined module finder of {@code finder} and + * {@code runtimeImageFinder} that first looks-up modules in the + * {@code runtimeImageFinder} and if not present in {@code finder}. + * + * @param runtimeImageFinder A system modules finder. + * @param finder A module finder based on packaged modules. + * @param limitMods The set of limited modules for the resulting + * finder (if any). + * @param roots All module roots. + * + * @return A combined finder, or the input finder, potentially applying + * module limits. + */ + private ModuleFinder combinedFinders(ModuleFinder runtimeImageFinder, + ModuleFinder finder, + Set limitMods, + Set roots) { + ModuleFinder combined = new ModuleFinder() { + + @Override + public Optional find(String name) { + Optional mref = runtimeImageFinder.find(name); + if (mref.isEmpty()) { + return finder.find(name); + } + return mref; + } + + @Override + public Set findAll() { + Set all = new HashSet<>(); + all.addAll(runtimeImageFinder.findAll()); + all.addAll(finder.findAll()); + return Collections.unmodifiableSet(all); + } + }; + // if limitmods is specified then limit the universe + if (limitMods != null && !limitMods.isEmpty()) { + return limitFinder(combined, limitMods, Objects.requireNonNull(roots)); + } + return combined; } private void createImage(JlinkConfiguration config) throws Exception { @@ -413,6 +489,7 @@ private void createImage(JlinkConfiguration config) throws Exception { options.bindServices, options.endian, options.verbose, + options, log); // Then create the Plugin Stack @@ -433,10 +510,10 @@ public static Path getDefaultModulePath() { } /* - * Returns a module finder of the given module path that limits - * the observable modules to those in the transitive closure of - * the modules specified in {@code limitMods} plus other modules - * specified in the {@code roots} set. + * Returns a module finder of the given module path or the system modules + * if the module path is empty that limits the observable modules to those + * in the transitive closure of the modules specified in {@code limitMods} + * plus other modules specified in the {@code roots} set. * * @throws IllegalArgumentException if java.base module is present * but its descriptor has no version @@ -445,14 +522,10 @@ public static ModuleFinder newModuleFinder(List paths, Set limitMods, Set roots) { - if (Objects.requireNonNull(paths).isEmpty()) { - throw new IllegalArgumentException(taskHelper.getMessage("err.empty.module.path")); - } - - Path[] entries = paths.toArray(new Path[0]); Runtime.Version version = Runtime.version(); - ModuleFinder finder = ModulePath.of(version, true, entries); - + Path[] entries = paths.toArray(new Path[0]); + ModuleFinder finder = paths.isEmpty() ? ModuleFinder.ofSystem() + : ModulePath.of(version, true, entries); if (finder.find("java.base").isPresent()) { // use the version of java.base module, if present, as // the release version for multi-release JAR files @@ -505,8 +578,9 @@ public FileVisitResult postVisitDirectory(Path dir, IOException e) private static Path toPathLocation(ResolvedModule m) { Optional ouri = m.reference().location(); - if (ouri.isEmpty()) + if (ouri.isEmpty()) { throw new InternalError(m + " does not have a location"); + } URI uri = ouri.get(); return Paths.get(uri); } @@ -518,6 +592,7 @@ private static ImageHelper createImageProvider(JlinkConfiguration config, boolean bindService, ByteOrder endian, boolean verbose, + OptionsValues opts, PrintWriter log) throws IOException { @@ -534,12 +609,35 @@ private static ImageHelper createImageProvider(JlinkConfiguration config, taskHelper.getMessage("err.automatic.module", mref.descriptor().name(), loc)); }); + // Perform some sanity checks for linking from the run-time image + if (config.linkFromRuntimeImage()) { + if (!LinkableRuntimeImage.isLinkableRuntime()) { + String msg = taskHelper.getMessage("err.runtime.link.not.linkable.runtime"); + throw new IllegalArgumentException(msg); + } + // Do not permit linking from run-time image and also including jdk.jlink module + if (cf.findModule(JlinkTask.class.getModule().getName()).isPresent()) { + String msg = taskHelper.getMessage("err.runtime.link.jdk.jlink.prohibited"); + throw new IllegalArgumentException(msg); + } + + // Print info message indicating linking from the run-time image + if (verbose && log != null) { + log.println(taskHelper.getMessage("runtime.link.info")); + } + } + if (verbose && log != null) { // print modules to be linked in cf.modules().stream() .sorted(Comparator.comparing(ResolvedModule::name)) - .forEach(rm -> log.format("%s %s%n", - rm.name(), rm.reference().location().get())); + .forEach(rm -> log.format("%s %s%s%n", + rm.name(), + rm.reference().location().get(), + // We have a link from run-time image when scheme is 'jrt' + "jrt".equals(rm.reference().location().get().getScheme()) + ? " " + taskHelper.getMessage("runtime.link.jprt.path.extra") + : "")); // print provider info Set references = cf.modules().stream() @@ -559,14 +657,15 @@ private static ImageHelper createImageProvider(JlinkConfiguration config, .map(ModuleDescriptor::name) .collect(Collectors.joining(", ")); - if (!"".equals(im)) + if (!"".equals(im)) { log.println("WARNING: Using incubator modules: " + im); + } } Map mods = cf.modules().stream() .collect(Collectors.toMap(ResolvedModule::name, JlinkTask::toPathLocation)); // determine the target platform of the image being created - Platform targetPlatform = targetPlatform(cf, mods); + Platform targetPlatform = targetPlatform(cf, mods, config.linkFromRuntimeImage()); // if the user specified any --endian, then it must match the target platform's native // endianness if (endian != null && endian != targetPlatform.arch().byteOrder()) { @@ -580,7 +679,92 @@ private static ImageHelper createImageProvider(JlinkConfiguration config, targetPlatform.arch().byteOrder(), targetPlatform); } } - return new ImageHelper(cf, mods, targetPlatform, retainModulesPath, ignoreSigning); + + // use the version of java.base module, if present, as + // the release version for multi-release JAR files + var version = cf.findModule("java.base") + .map(ResolvedModule::reference) + .map(ModuleReference::descriptor) + .flatMap(ModuleDescriptor::version) + .map(ModuleDescriptor.Version::toString) + .map(Runtime.Version::parse) + .orElse(Runtime.version()); + + Set archives = mods.entrySet().stream() + .map(e -> newArchive(e.getKey(), + e.getValue(), + version, + ignoreSigning, + config, + log)) + .collect(Collectors.toSet()); + + return new ImageHelper(archives, + targetPlatform, + retainModulesPath, + config.isGenerateRuntimeImage()); + } + + private static Archive newArchive(String module, + Path path, + Runtime.Version version, + boolean ignoreSigning, + JlinkConfiguration config, + PrintWriter log) { + if (path.toString().endsWith(".jmod")) { + return new JmodArchive(module, path); + } else if (path.toString().endsWith(".jar")) { + ModularJarArchive modularJarArchive = new ModularJarArchive(module, path, version); + try (Stream entries = modularJarArchive.entries()) { + boolean hasSignatures = entries.anyMatch((entry) -> { + String name = entry.name().toUpperCase(Locale.ROOT); + + return name.startsWith("META-INF/") && name.indexOf('/', 9) == -1 && ( + name.endsWith(".SF") || + name.endsWith(".DSA") || + name.endsWith(".RSA") || + name.endsWith(".EC") || + name.startsWith("META-INF/SIG-") + ); + }); + + if (hasSignatures) { + if (ignoreSigning) { + System.err.println(taskHelper.getMessage("warn.signing", path)); + } else { + throw new IllegalArgumentException(taskHelper.getMessage("err.signing", path)); + } + } + } + return modularJarArchive; + } else if (Files.isDirectory(path) && !"jrt".equals(path.toUri().getScheme())) { + // The jrt URI path scheme conditional is there since we'd otherwise + // enter this branch for linking from the run-time image where the + // path is a jrt path. Note that the specific module would be a + // directory. I.e. Files.isDirectory() would be true. + Path modInfoPath = path.resolve("module-info.class"); + if (Files.isRegularFile(modInfoPath)) { + return new DirArchive(path, findModuleName(modInfoPath)); + } else { + throw new IllegalArgumentException( + taskHelper.getMessage("err.not.a.module.directory", path)); + } + } else if (config.linkFromRuntimeImage()) { + return LinkableRuntimeImage.newArchive(module, path, config.ignoreModifiedRuntime(), taskHelper); + } else { + throw new IllegalArgumentException( + taskHelper.getMessage("err.not.modular.format", module, path)); + } + } + + private static String findModuleName(Path modInfoPath) { + try (BufferedInputStream bis = new BufferedInputStream( + Files.newInputStream(modInfoPath))) { + return ModuleDescriptor.read(bis).name(); + } catch (IOException exp) { + throw new IllegalArgumentException(taskHelper.getMessage( + "err.cannot.read.module.info", modInfoPath), exp); + } } /* @@ -626,10 +810,12 @@ public Set findAll() { }; } - private static Platform targetPlatform(Configuration cf, Map modsPaths) throws IOException { + private static Platform targetPlatform(Configuration cf, + Map modsPaths, + boolean runtimeImageLink) throws IOException { Path javaBasePath = modsPaths.get("java.base"); assert javaBasePath != null : "java.base module path is missing"; - if (isJavaBaseFromDefaultModulePath(javaBasePath)) { + if (runtimeImageLink || isJavaBaseFromDefaultModulePath(javaBasePath)) { // this implies that the java.base module used for the target image // will correspond to the current platform. So this isn't an attempt to // build a cross-platform image. We use the current platform's endianness @@ -720,8 +906,9 @@ private static void printProviders(PrintWriter log, String header, Set modules, Map> serviceToUses) { - if (modules.isEmpty()) + if (modules.isEmpty()) { return; + } // Build a map of a service type to the provider modules Map> providers = new HashMap<>(); @@ -845,95 +1032,14 @@ private String getSaveOpts() { return sb.toString(); } - private static class ImageHelper implements ImageProvider { - final Platform targetPlatform; - final Path packagedModulesPath; - final boolean ignoreSigning; - final Runtime.Version version; - final Set archives; - - ImageHelper(Configuration cf, - Map modsPaths, - Platform targetPlatform, - Path packagedModulesPath, - boolean ignoreSigning) throws IOException { - Objects.requireNonNull(targetPlatform); - this.targetPlatform = targetPlatform; - this.packagedModulesPath = packagedModulesPath; - this.ignoreSigning = ignoreSigning; - - // use the version of java.base module, if present, as - // the release version for multi-release JAR files - this.version = cf.findModule("java.base") - .map(ResolvedModule::reference) - .map(ModuleReference::descriptor) - .flatMap(ModuleDescriptor::version) - .map(ModuleDescriptor.Version::toString) - .map(Runtime.Version::parse) - .orElse(Runtime.version()); - - this.archives = modsPaths.entrySet().stream() - .map(e -> newArchive(e.getKey(), e.getValue())) - .collect(Collectors.toSet()); - } - - private Archive newArchive(String module, Path path) { - if (path.toString().endsWith(".jmod")) { - return new JmodArchive(module, path); - } else if (path.toString().endsWith(".jar")) { - ModularJarArchive modularJarArchive = new ModularJarArchive(module, path, version); - - try (Stream entries = modularJarArchive.entries()) { - boolean hasSignatures = entries.anyMatch((entry) -> { - String name = entry.name().toUpperCase(Locale.ROOT); - - return name.startsWith("META-INF/") && name.indexOf('/', 9) == -1 && ( - name.endsWith(".SF") || - name.endsWith(".DSA") || - name.endsWith(".RSA") || - name.endsWith(".EC") || - name.startsWith("META-INF/SIG-") - ); - }); - - if (hasSignatures) { - if (ignoreSigning) { - System.err.println(taskHelper.getMessage("warn.signing", path)); - } else { - throw new IllegalArgumentException(taskHelper.getMessage("err.signing", path)); - } - } - } - - return modularJarArchive; - } else if (Files.isDirectory(path)) { - Path modInfoPath = path.resolve("module-info.class"); - if (Files.isRegularFile(modInfoPath)) { - return new DirArchive(path, findModuleName(modInfoPath)); - } else { - throw new IllegalArgumentException( - taskHelper.getMessage("err.not.a.module.directory", path)); - } - } else { - throw new IllegalArgumentException( - taskHelper.getMessage("err.not.modular.format", module, path)); - } - } - - private static String findModuleName(Path modInfoPath) { - try (BufferedInputStream bis = new BufferedInputStream( - Files.newInputStream(modInfoPath))) { - return ModuleDescriptor.read(bis).name(); - } catch (IOException exp) { - throw new IllegalArgumentException(taskHelper.getMessage( - "err.cannot.read.module.info", modInfoPath), exp); - } - } - + private static record ImageHelper(Set archives, + Platform targetPlatform, + Path packagedModulesPath, + boolean generateRuntimeImage) implements ImageProvider { @Override public ExecutableImage retrieve(ImagePluginStack stack) throws IOException { ExecutableImage image = ImageFileCreator.create(archives, - targetPlatform.arch().byteOrder(), stack); + targetPlatform.arch().byteOrder(), stack, generateRuntimeImage, taskHelper); if (packagedModulesPath != null) { // copy the packaged modules to the given path Files.createDirectories(packagedModulesPath); diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/LinkableRuntimeImage.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/LinkableRuntimeImage.java new file mode 100644 index 0000000000000..935af4585ad7a --- /dev/null +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/LinkableRuntimeImage.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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.tools.jlink.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.List; + +import jdk.tools.jlink.internal.runtimelink.ResourceDiff; + +/** + * Class that supports the feature of running jlink based on the current + * run-time image. + */ +public class LinkableRuntimeImage { + + // meta-data files per module for supporting linking from the run-time image + public static final String RESPATH_PATTERN = "jdk/tools/jlink/internal/runtimelink/fs_%s_files"; + // The diff files per module for supporting linking from the run-time image + public static final String DIFF_PATTERN = "jdk/tools/jlink/internal/runtimelink/diff_%s"; + + /** + * In order to be able to show whether or not a runtime is capable of + * linking from it in {@code jlink --help} we need to look for the delta + * files in the {@code jdk.jlink} module. If present we have the capability. + * + * @return {@code true} iff this jlink is capable of linking from the + * run-time image. + */ + public static boolean isLinkableRuntime() { + try (InputStream in = getDiffInputStream("java.base")) { + return in != null; + } catch (IOException e) { + // fall-through + } + return false; + } + + private static InputStream getDiffInputStream(String module) throws IOException { + String resourceName = String.format(DIFF_PATTERN, module); + return LinkableRuntimeImage.class.getModule().getResourceAsStream(resourceName); + } + + public static Archive newArchive(String module, + Path path, + boolean ignoreModifiedRuntime, + TaskHelper taskHelper) { + assert isLinkableRuntime(); + // Here we retrieve the per module difference file, which is + // potentially empty, from the modules image and pass that on to + // JRTArchive for further processing. When streaming resources from + // the archive, the diff is being applied. + List perModuleDiff = null; + try (InputStream in = getDiffInputStream(module)){ + perModuleDiff = ResourceDiff.read(in); + } catch (IOException e) { + throw new AssertionError("Failure to retrieve resource diff for " + + "module " + module, e); + } + return new JRTArchive(module, path, !ignoreModifiedRuntime, perModuleDiff, taskHelper); + } + + +} diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java index 2b4e6ca0a976c..23b3dfb7079f5 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java @@ -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 @@ -28,23 +28,21 @@ import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.text.MessageFormat; -import java.util.Map; -import java.util.HashMap; -import java.util.Map.Entry; -import java.util.Set; -import java.util.HashSet; -import java.util.List; import java.util.ArrayList; import java.util.Arrays; -import java.util.stream.Stream; import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Locale; -import java.util.ResourceBundle; +import java.util.Map; +import java.util.Map.Entry; import java.util.MissingResourceException; -import java.util.Comparator; - +import java.util.ResourceBundle; +import java.util.Set; +import java.util.stream.Stream; import jdk.tools.jlink.builder.DefaultImageBuilder; import jdk.tools.jlink.builder.ImageBuilder; @@ -55,7 +53,6 @@ import jdk.tools.jlink.internal.plugins.PluginsResourceBundle; import jdk.tools.jlink.plugin.Plugin; import jdk.tools.jlink.plugin.Plugin.Category; -import jdk.tools.jlink.plugin.PluginException; /** * @@ -584,7 +581,7 @@ private Option getOption(String name) { return null; } - public void showHelp(String progName) { + public void showHelp(String progName, boolean linkableRuntimeEnabled) { log.println(bundleHelper.getMessage("main.usage", progName)); Stream.concat(options.stream(), pluginOptions.mainOptions.stream()) .filter(option -> !option.isHidden()) @@ -594,6 +591,17 @@ public void showHelp(String progName) { }); log.println(bundleHelper.getMessage("main.command.files")); + // If the JDK build has the run-time image capability show it + // in the help output in human readable form. + String qualifier = null; + if (linkableRuntimeEnabled) { + qualifier = bundleHelper.getMessage("main.runtime.image.linking.cap.enabled"); + } else { + qualifier = bundleHelper.getMessage("main.runtime.image.linking.cap.disabled"); + } + log.println(bundleHelper.getMessage("main.runtime.image.linking.cap.sect.header")); + log.println(bundleHelper.getMessage("main.runtime.image.linking.cap.msg", + qualifier)); } public void listPlugins() { diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/JimageDiffGenerator.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/JimageDiffGenerator.java new file mode 100644 index 0000000000000..5e540be7ced6a --- /dev/null +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/JimageDiffGenerator.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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.tools.jlink.internal.runtimelink; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Generates a delta between packaged modules (as an ImageResource) and an + * optimized jimage (lib/modules) as an ImageResource. The result can be + * serialized to a file using {@link ResourceDiff}. + */ +public class JimageDiffGenerator { + + /** + * A resource used for linking. Either packaged modules or + * packaged modules transformed to an optimized run-time image by applying + * the jlink plug-in pipeline. The canonical source, the packaged modules, + * are being used to devise the delta to the transformed run-time image. The + * delta can can then be used for jlink input together *with* a prepared + * run-time image. + */ + @SuppressWarnings("try") + public interface ImageResource extends AutoCloseable { + public List getEntries(); + public byte[] getResourceBytes(String name); + } + + /** + * Produce a difference between packaged modules' resources (base) and the + * result of all plug-ins being applied on those resources (image). + * + * @param base + * The ImageResource view of unmodified resources coming from + * packaged modules. + * @param image + * The ImageResource view of the jlink plug-in pipeline having + * been applied to the resources in base. + * @return The list of resource differences across all modules. + */ + public List generateDiff(ImageResource base, ImageResource image) throws Exception { + List baseResources; + Set resources = new HashSet<>(); + List diffs = new ArrayList<>(); + try (base; image) { + resources.addAll(image.getEntries()); + baseResources = base.getEntries(); + for (String item: baseResources) { + byte[] baseBytes = base.getResourceBytes(item); + // First check that every item in the base image exist in + // the optimized image as well. If it does not, it's a removed + // item in the optimized image. + if (!resources.remove(item)) { + // keep track of original bytes for removed item in the + // optimized image, since we need to restore them for the + // runtime image link + ResourceDiff.Builder builder = new ResourceDiff.Builder(); + ResourceDiff diff = builder.setKind(ResourceDiff.Kind.REMOVED) + .setName(item) + .setResourceBytes(baseBytes) + .build(); + diffs.add(diff); + continue; + } + // Verify resource bytes are equal if present in both images + boolean contentEquals = Arrays.equals(baseBytes, image.getResourceBytes(item)); + if (!contentEquals) { + // keep track of original bytes (non-optimized) + ResourceDiff.Builder builder = new ResourceDiff.Builder(); + ResourceDiff diff = builder.setKind(ResourceDiff.Kind.MODIFIED) + .setName(item) + .setResourceBytes(baseBytes) + .build(); + diffs.add(diff); + } + } + } + // What's now left in the set are the resources only present in the + // optimized image (generated by some plugins; not present in jmods) + for (String e: resources) { + ResourceDiff.Builder builder = new ResourceDiff.Builder(); + ResourceDiff diff = builder.setKind(ResourceDiff.Kind.ADDED) + .setName(e) + .build(); + diffs.add(diff); + } + return diffs; + } + +} diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/ResourceDiff.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/ResourceDiff.java new file mode 100644 index 0000000000000..a007350c16f81 --- /dev/null +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/ResourceDiff.java @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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.tools.jlink.internal.runtimelink; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Class representing a difference of a jimage resource. For all intents + * and purposes this represents a difference between a resource in an optimized + * jimage (e.g. images/jdk/lib/modules) and the underlying basic resources from + * which the optimized image got derived from (e.g. packaged modules). The + * differences are being used in JRTArchive so as to back-track from an optimized + * jimage to the original (i.e. it restores original resources using the diff). + */ +public class ResourceDiff implements Comparable { + + private static final int MAGIC = 0xabba; + + public static enum Kind { + ADDED((short)1), // Resource added + REMOVED((short)2), // Resource removed + MODIFIED((short)3); // Resource modified + + private short value; + + private Kind(short value) { + this.value = value; + } + + public short value() { + return value; + } + + static Kind fromShort(short v) { + if (v > 3 || v < 1) { + throw new IllegalArgumentException("Must be within range [1-3]"); + } + switch (v) { + case 1: return ADDED; + case 2: return REMOVED; + case 3: return MODIFIED; + } + throw new AssertionError("Must not reach here!"); + } + } + + private final Kind kind; + private final byte[] resourceBytes; + private final String name; + + private ResourceDiff(Kind kind, String name, byte[] resourceBytes) { + this.kind = kind; + this.name = name; + if ((kind == Kind.REMOVED || kind == Kind.MODIFIED) && + resourceBytes == null) { + throw new AssertionError("Resource bytes must be set for REMOVED or MODIFIED"); + } + this.resourceBytes = resourceBytes; + } + + public Kind getKind() { + return kind; + } + + public byte[] getResourceBytes() { + return resourceBytes; + } + + public String getName() { + return name; + } + + @Override + public int compareTo(ResourceDiff o) { + int kindComp = kind.value() - o.kind.value(); + if (kindComp == 0) { + return getName().compareTo(o.getName()); + } else { + return kindComp; + } + } + + public static class Builder { + private Kind kind; + private String name; + private byte[] resourceBytes; + + public Builder setKind(Kind kind) { + this.kind = kind; + return this; + } + public Builder setName(String name) { + this.name = Objects.requireNonNull(name); + return this; + } + public Builder setResourceBytes(byte[] resourceBytes) { + this.resourceBytes = Objects.requireNonNull(resourceBytes); + return this; + } + public ResourceDiff build() { + if (kind == null || name == null) { + throw new AssertionError("kind and name must be set"); + } + switch (kind) { + case ADDED: + { + break; // null bytes for added is OK. + } + case MODIFIED: // fall-through + case REMOVED: + { + if (resourceBytes == null) { + throw new AssertionError("Original bytes needed for MODIFIED, REMOVED!"); + } + break; + } + default: + break; + } + return new ResourceDiff(kind, name, resourceBytes); + } + } + + /** + * Writes a list of resource diffs to an output stream + * + * @param diffs The list of resource diffs to write. + * @param out The stream to write the serialized bytes to. + */ + public static void write(List diffs, OutputStream out) throws IOException { + /* + * Simple binary format: + * + *

| + * + * **************************************** + * HEADER info + * **************************************** + * + * where
is ('|' separation for clarity): + * + * | + * + * The first integer is the MAGIC, 0xabba. The second integer is the + * total number of items. + * + * ***************************************** + * ITEMS info + * ***************************************** + * + * Each consists of ('|' separation for clarity): + * + * |||| + * + * Where the individual items are: + * + * : + * The value of the respective ResourceDiff.Kind. + * : + * The length of the name bytes (in UTF-8). + * : + * The resource name bytes in UTF-8. + * : + * The length of the resource bytes. 0 (zero) if no resource bytes. + * A.k.a 'null'. + * : + * The bytes of the resource as stored in the jmod files. + */ + try (DataOutputStream dataOut = new DataOutputStream(out)) { + dataOut.writeInt(MAGIC); + dataOut.writeInt(diffs.size()); + for (ResourceDiff d: diffs) { + dataOut.writeShort(d.kind.value()); + byte[] buf = d.name.getBytes(StandardCharsets.UTF_8); + dataOut.writeInt(buf.length); + dataOut.write(buf); + buf = d.resourceBytes; + dataOut.writeInt(buf == null ? 0 : buf.length); + if (buf != null) { + dataOut.write(buf); + } + } + } + } + + /** + * Read a list of resource diffs from an input stream. + * + * @param in The input stream to read from + * @return The list of resource diffs. + */ + public static List read(InputStream in) throws IOException { + /* + * See write() for the details how this is being written + */ + List diffs = new ArrayList<>(); + try (DataInputStream din = new DataInputStream(in)) { + int magic = din.readInt(); + if (magic != MAGIC) { + throw new IllegalArgumentException("Not a ResourceDiff data stream!"); + } + int numItems = din.readInt(); + for (int i = 0; i < numItems; i++) { + Kind k = Kind.fromShort(din.readShort()); + int numBytes = din.readInt(); + byte[] buf = readBytesFromStream(din, numBytes); + String name = new String(buf, StandardCharsets.UTF_8); + numBytes = din.readInt(); + byte[] resBytes = null; + if (numBytes != 0) { + resBytes = readBytesFromStream(din, numBytes); + } + Builder builder = new Builder(); + builder.setKind(k) + .setName(name); + if (resBytes != null) { + builder.setResourceBytes(resBytes); + } + diffs.add(builder.build()); + } + } + return Collections.unmodifiableList(diffs); + } + + private static byte[] readBytesFromStream(DataInputStream din, int numBytes) throws IOException { + byte[] b = new byte[numBytes]; + for (int i = 0; i < numBytes; i++) { + int data = din.read(); + if (data == -1) { + throw new IOException("Short read!"); + } + b[i] = (byte)data; + } + return b; + } + + public static void printDiffs(List diffs) { + for (ResourceDiff diff: diffs.stream().sorted().toList()) { + switch (diff.getKind()) { + case ADDED: + System.out.println("Only added in opt: " + diff.getName()); + break; + case MODIFIED: + System.out.println("Modified in opt: " + diff.getName()); + break; + case REMOVED: + System.out.println("Removed in opt: " + diff.getName()); + break; + default: + break; + } + } + } + +} diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/ResourcePoolReader.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/ResourcePoolReader.java new file mode 100644 index 0000000000000..12e8708477c53 --- /dev/null +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/ResourcePoolReader.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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.tools.jlink.internal.runtimelink; + +import java.util.List; +import java.util.Objects; + +import jdk.tools.jlink.internal.runtimelink.JimageDiffGenerator.ImageResource; +import jdk.tools.jlink.plugin.ResourcePool; +import jdk.tools.jlink.plugin.ResourcePoolEntry; + +@SuppressWarnings("try") +public class ResourcePoolReader implements ImageResource { + + private final ResourcePool pool; + + public ResourcePoolReader(ResourcePool pool) { + this.pool = Objects.requireNonNull(pool); + } + + @Override + public void close() throws Exception { + // nothing + } + + @Override + public List getEntries() { + return pool.entries().map(ResourcePoolEntry::path).toList(); + } + + @Override + public byte[] getResourceBytes(String name) { + return pool.findEntry(name).orElseThrow().contentBytes(); + } + +} diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/RuntimeImageLinkException.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/RuntimeImageLinkException.java new file mode 100644 index 0000000000000..9f54fd63476ff --- /dev/null +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/RuntimeImageLinkException.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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.tools.jlink.internal.runtimelink; + +import java.util.Objects; + +/** + * Exception thrown when linking from the run-time image + */ +public class RuntimeImageLinkException extends RuntimeException { + + private static final long serialVersionUID = -1848914673073119403L; + + public static enum Reason { + PATCH_MODULE, /* link exception due to patched module */ + MODIFIED_FILE, /* link exception due to modified file */ + } + + private final String file; + private final Reason reason; + + public RuntimeImageLinkException(String file, Reason reason) { + this.file = Objects.requireNonNull(file); + this.reason = Objects.requireNonNull(reason); + } + + public String getFile() { + return file; + } + + public Reason getReason() { + return reason; + } + + @Override + public String getMessage() { + return reason + ", file: " + file; + } +} diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties index e09eadbb3d0ff..9e18177d9c84f 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties @@ -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 @@ -111,10 +111,22 @@ main.extended.help.footer=\ \ used, one pattern per line\n\ \n\ +main.runtime.image.linking.cap.enabled=enabled +main.runtime.image.linking.cap.disabled=disabled +main.runtime.image.linking.cap.sect.header=Capabilities: +main.runtime.image.linking.cap.msg=\ Linking from run-time image {0} error.prefix=Error: warn.prefix=Warning: +err.runtime.link.not.linkable.runtime=This JDK does not support linking from the current run-time image +err.runtime.link.jdk.jlink.prohibited=This JDK does not contain packaged modules\ +\ and cannot be used to create another image with the jdk.jlink module +err.runtime.link.packaged.mods=This JDK has no packaged modules.\ +\ --keep-packaged-modules is not supported +err.runtime.link.modified.file={0} has been modified +err.runtime.link.patched.module=File {0} not found in the modules image.\ +\ --patch-module is not supported when linking from the run-time image err.empty.module.path=empty module path err.jlink.version.mismatch=jlink version {0}.{1} does not match target java.base version {2}.{3} err.automatic.module:automatic module cannot be used with jlink: {0} from {1} @@ -123,7 +135,7 @@ err.launcher.main.class.empty:launcher main class name cannot be empty: {0} err.launcher.module.name.empty:launcher module name cannot be empty: {0} err.launcher.value.format:launcher value should be of form =[/]: {0} err.output.must.be.specified:--output must be specified -err.modulepath.must.be.specified:--module-path is not specified and this runtime image does not contain jmods directory. +err.modulepath.must.be.specified:--module-path is not specified and this run-time image does not contain a jmods directory err.mods.must.be.specified:no modules specified to {0} err.path.not.found=path not found: {0} err.path.not.valid=invalid path: {0} @@ -157,3 +169,6 @@ warn.provider.notfound=No provider found for service specified to --suggest-prov no.suggested.providers=--bind-services option is specified. No additional providers suggested. suggested.providers.header=Suggested providers providers.header=Providers + +runtime.link.info=Linking based on the current run-time image +runtime.link.jprt.path.extra=(run-time image) diff --git a/src/jdk.jshell/share/classes/jdk/jshell/Eval.java b/src/jdk.jshell/share/classes/jdk/jshell/Eval.java index 0ee6a926b41ca..bc6f6d30236df 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/Eval.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/Eval.java @@ -176,7 +176,16 @@ List sourceToSnippetsWithWrappers(String userSource) { List toScratchSnippets(String userSource) { try { preserveState = true; - return sourceToSnippets(userSource); + List result = sourceToSnippetsWithWrappers(userSource); + result.forEach(snippet -> { + if (snippet.diagnostics() == null || snippet.diagnostics().isEmpty()) { + //if no better diagnostics set yet, do trial compilation, and + //set diagnostic found: + DiagList fullDiagnostics = state.taskFactory.analyze(snippet.outerWrap(), AnalyzeTask::getDiagnostics); + snippet.setDiagnostics(fullDiagnostics); + } + }); + return result; } finally { preserveState = false; } diff --git a/src/jdk.jshell/share/classes/jdk/jshell/JShell.java b/src/jdk.jshell/share/classes/jdk/jshell/JShell.java index ffa46cf8be4e1..4ca8e3830c8c0 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/JShell.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/JShell.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2017, 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 @@ -678,6 +678,12 @@ public Status status(Snippet snippet) { * Return the diagnostics of the most recent evaluation of the snippet. * The evaluation can either because of an explicit {@code eval()} call or * an automatic update triggered by a dependency. + * + *

This method will return best-effort diagnostics for snippets returned + * from {@link SourceCodeAnalysis#sourceToSnippets(java.lang.String) }. The + * diagnostics returned for such snippets may differ from diagnostics provided + * after the snippet is {@link #eval(java.lang.String) }-ed. + * * @param snippet the {@code Snippet} to look up * @return the diagnostics corresponding to this snippet. This does not * include unresolvedDependencies references reported in {@code unresolvedDependencies()}. @@ -686,7 +692,7 @@ public Status status(Snippet snippet) { * this {@code JShell} instance. */ public Stream diagnostics(Snippet snippet) { - return checkValidSnippet(snippet).diagnostics().stream(); + return checkValidSnippet(snippet, true).diagnostics().stream(); } /** @@ -901,10 +907,22 @@ void checkIfAlive() throws IllegalStateException { * @return the input Snippet (for chained calls) */ private Snippet checkValidSnippet(Snippet sn) { + return checkValidSnippet(sn, false); + } + + /** + * Check a Snippet parameter coming from the API user + * @param sn the Snippet to check + * @param acceptUnassociated accept snippets that are unassociated + * @throws NullPointerException if Snippet parameter is null + * @throws IllegalArgumentException if Snippet is not from this JShell + * @return the input Snippet (for chained calls) + */ + private Snippet checkValidSnippet(Snippet sn, boolean acceptUnassociated) { if (sn == null) { throw new NullPointerException(messageFormat("jshell.exc.null")); } else { - if (sn.key().state() != this || sn.id() == Snippet.UNASSOCIATED_ID) { + if (sn.key().state() != this || (!acceptUnassociated && sn.id() == Snippet.UNASSOCIATED_ID)) { throw new IllegalArgumentException(messageFormat("jshell.exc.alien", sn.toString())); } return sn; diff --git a/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java b/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java index 99bfd870f37c3..0375a2ead6529 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java @@ -145,7 +145,8 @@ public abstract class SourceCodeAnalysis { * will be {@code "*UNASSOCIATED*"}. * The returned snippets are not associated with the * {@link JShell} instance, so attempts to pass them to {@code JShell} - * methods will throw an {@code IllegalArgumentException}. + * methods will throw an {@code IllegalArgumentException}, unless otherwise + * noted. * They will not appear in queries for snippets -- * for example, {@link JShell#snippets() }. *

diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java index e47518de4a722..f9c2e991d4631 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramScene.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 @@ -222,6 +222,17 @@ public void filteredChanged(SelectionCoordinator coordinator) { } }; + public void colorSelectedFigures(Color color) { + for (Figure figure : model.getSelectedFigures()) { + figure.setColor(color); + FigureWidget figureWidget = getWidget(figure); + if (figureWidget != null) { + figureWidget.refreshColor(); + } + } + validateAll(); + } + private Point getScrollPosition() { return scrollPane.getViewport().getViewPosition(); } diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramViewer.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramViewer.java index 591bbe371a3d1..e8cfd2968dc29 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramViewer.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/DiagramViewer.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 @@ -26,10 +26,7 @@ import com.sun.hotspot.igv.data.ChangedEvent; import com.sun.hotspot.igv.data.InputNode; -import java.awt.Component; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.Rectangle; +import java.awt.*; import java.util.Collection; import javax.swing.JComponent; import org.openide.awt.UndoRedo; @@ -89,4 +86,6 @@ enum InteractionMode { Rectangle getBounds(); JComponent getView(); + + void colorSelectedFigures(Color color); } diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java index 617f59a591d4d..e24ffa476a25d 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/EditorTopComponent.java @@ -99,6 +99,7 @@ public EditorTopComponent(DiagramViewModel diagramViewModel) { }; Action[] actionsWithSelection = new Action[]{ + ColorAction.get(ColorAction.class), ExtractAction.get(ExtractAction.class), HideAction.get(HideAction.class), null, @@ -349,6 +350,10 @@ public void addSelectedNodes(Collection nodes, boolean showIfHidden) scene.addSelectedNodes(nodes, showIfHidden); } + public void colorSelectedFigures(Color color) { + scene.colorSelectedFigures(color); + } + public void centerSelectedNodes() { scene.centerSelectedFigures(); } diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ColorAction.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ColorAction.java new file mode 100644 index 0000000000000..7a38587eb3855 --- /dev/null +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ColorAction.java @@ -0,0 +1,170 @@ +/* + * 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 com.sun.hotspot.igv.view.actions; + +import com.sun.hotspot.igv.view.DiagramViewModel; +import com.sun.hotspot.igv.view.EditorTopComponent; +import com.sun.hotspot.igv.view.widgets.FigureWidget; +import java.awt.*; +import java.util.ArrayList; +import java.util.Arrays; +import javax.swing.*; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionReferences; +import org.openide.awt.ActionRegistration; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; + + +@ActionID(category = "View", id = "com.sun.hotspot.igv.view.actions.ColorAction") +@ActionRegistration(displayName = "#CTL_ColorAction") +@ActionReferences({ + @ActionReference(path = "Menu/View", position = 360), + @ActionReference(path = "Shortcuts", name = "D-C") +}) +@Messages({ + "CTL_ColorAction=Color", + "HINT_ColorAction=Color current set of selected nodes" +}) +public final class ColorAction extends ModelAwareAction { + + @Override + protected String iconResource() { + return "com/sun/hotspot/igv/view/images/color.gif"; // NOI18N + } + + @Override + protected String getDescription() { + return NbBundle.getMessage(ColorAction.class, "HINT_ColorAction"); + } + + @Override + public String getName() { + return NbBundle.getMessage(ColorAction.class, "CTL_ColorAction"); + } + + private static final ArrayList colors = new ArrayList<>(Arrays.asList( + Color.RED, + Color.ORANGE, + Color.YELLOW, + Color.GREEN, + Color.CYAN, + Color.BLUE, + Color.MAGENTA, + Color.PINK, + Color.DARK_GRAY, + Color.GRAY, + Color.LIGHT_GRAY, + Color.WHITE + )); + + private static final JLabel selectedColorLabel = new JLabel("Preview"); + private static final JColorChooser colorChooser = new JColorChooser(Color.WHITE); + + public ColorAction() { + initializeComponents(); + } + + private void initializeComponents() { + selectedColorLabel.setPreferredSize(new Dimension(3 * 32, 32)); + selectedColorLabel.setOpaque(true); + selectedColorLabel.setBackground(Color.WHITE); + selectedColorLabel.setForeground(Color.BLACK); // Set text color + selectedColorLabel.setHorizontalAlignment(SwingConstants.CENTER); // Center the text + + + // Add a ChangeListener to react to color selection changes + colorChooser.getSelectionModel().addChangeListener(e -> { + Color selectedColor = colorChooser.getColor(); + if (selectedColor != null) { + selectedColorLabel.setBackground(selectedColor); + selectedColorLabel.setForeground(FigureWidget.getTextColor(selectedColor)); + } + }); + + // Create a panel to display recent colors + JPanel colorsPanel = new JPanel(); + colorsPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); + for (Color color : colors) { + JButton colorButton = new JButton(); + colorButton.setBackground(color); + colorButton.setOpaque(true); + colorButton.setBorderPainted(false); + colorButton.setRolloverEnabled(false); + colorButton.setRequestFocusEnabled(false); + + colorButton.setPreferredSize(new Dimension(16, 16)); + colorButton.addActionListener(e -> { + selectedColorLabel.setBackground(color); + selectedColorLabel.setForeground(FigureWidget.getTextColor(color)); + }); + colorsPanel.add(colorButton); + } + colorsPanel.add(selectedColorLabel, 0); + colorsPanel.revalidate(); + colorsPanel.repaint(); + + // Add recent colors panel below the color chooser + colorChooser.setPreviewPanel(colorsPanel); + } + + // Variables to store the dialog position + private Point dialogLoc = null; + + public void performAction(DiagramViewModel model) { + EditorTopComponent editor = EditorTopComponent.getActive(); + if (editor != null) { + // Create the dialog with an OK button to select the color + final JDialog[] dialogHolder = new JDialog[1]; + dialogHolder[0] = JColorChooser.createDialog( + null, + "Choose a Color", + true, + colorChooser, + e -> { + // Save the current location + dialogLoc = dialogHolder[0].getLocation(); + // OK button action + Color selectedColor = selectedColorLabel.getBackground(); + if (selectedColor != null) { + editor.colorSelectedFigures(selectedColor); + } + }, + null // Cancel button action + ); + + // Set the dialog's position if previously saved + if (dialogLoc != null) { + dialogHolder[0].setLocation(dialogLoc); + } + dialogHolder[0].setVisible(true); + } + } + + @Override + public boolean isEnabled(DiagramViewModel model) { + return model != null && !model.getSelectedNodes().isEmpty(); + } +} diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ExtractAction.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ExtractAction.java index 24815527a0eec..24547b19b6a30 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ExtractAction.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/actions/ExtractAction.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 @@ -42,7 +42,7 @@ @ActionReference(path = "Shortcuts", name = "D-X") }) @Messages({ - "CTL_ExtractAction=Extract action", + "CTL_ExtractAction=Extract", "HINT_ExtractAction=Extract current set of selected nodes" }) public final class ExtractAction extends ModelAwareAction { diff --git a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java index 7ac76fdefbada..bbdf08dc8b84b 100644 --- a/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.java +++ b/src/utils/IdealGraphVisualizer/View/src/main/java/com/sun/hotspot/igv/view/widgets/FigureWidget.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 @@ -90,7 +90,20 @@ private void formatExtraLabel(boolean selected) { if (getFigure().getProperties().get("extra_label") != null) { LabelWidget extraLabelWidget = labelWidgets.get(labelWidgets.size() - 1); extraLabelWidget.setFont(Diagram.FONT.deriveFont(Font.ITALIC)); - extraLabelWidget.setForeground(selected ? getTextColor() : Color.DARK_GRAY); + extraLabelWidget.setForeground(getTextColorHelper(figure.getColor(), !selected)); + } + } + + public static Color getTextColor(Color color) { + return getTextColorHelper(color, false); + } + + private static Color getTextColorHelper(Color bg, boolean useGrey) { + double brightness = bg.getRed() * 0.21 + bg.getGreen() * 0.72 + bg.getBlue() * 0.07; + if (brightness < 150) { + return useGrey ? Color.LIGHT_GRAY : Color.WHITE; + } else { + return useGrey ? Color.DARK_GRAY : Color.BLACK; } } @@ -113,7 +126,6 @@ public FigureWidget(final Figure f, DiagramScene scene) { LayoutFactory.SerialAlignment.LEFT_TOP : LayoutFactory.SerialAlignment.CENTER; middleWidget.setLayout(LayoutFactory.createVerticalFlowLayout(textAlign, 0)); - middleWidget.setBackground(f.getColor()); middleWidget.setOpaque(true); middleWidget.getActions().addAction(new DoubleClickAction(this)); middleWidget.setCheckClipping(false); @@ -143,13 +155,13 @@ public FigureWidget(final Figure f, DiagramScene scene) { textWidget.addChild(lw); lw.setLabel(displayString); lw.setFont(Diagram.FONT); - lw.setForeground(getTextColor()); lw.setAlignment(LabelWidget.Alignment.CENTER); lw.setVerticalAlignment(LabelWidget.VerticalAlignment.CENTER); lw.setBorder(BorderFactory.createEmptyBorder()); lw.setCheckClipping(false); } formatExtraLabel(false); + refreshColor(); if (getFigure().getWarning() != null) { ImageWidget warningWidget = new ImageWidget(scene, warningSign); @@ -184,6 +196,13 @@ protected Sheet createSheet() { this.setToolTipText(PropertiesConverter.convertToHTML(f.getProperties())); } + public void refreshColor() { + middleWidget.setBackground(figure.getColor()); + for (LabelWidget lw : labelWidgets) { + lw.setForeground(getTextColor(figure.getColor())); + } + } + @Override protected void notifyStateChanged(ObjectState previousState, ObjectState state) { super.notifyStateChanged(previousState, state); @@ -222,16 +241,6 @@ public Figure getFigure() { return figure; } - private Color getTextColor() { - Color bg = figure.getColor(); - double brightness = bg.getRed() * 0.21 + bg.getGreen() * 0.72 + bg.getBlue() * 0.07; - if (brightness < 150) { - return Color.WHITE; - } else { - return Color.BLACK; - } - } - @Override protected void paintChildren() { Composite oldComposite = null; diff --git a/src/utils/IdealGraphVisualizer/View/src/main/resources/com/sun/hotspot/igv/view/images/color.gif b/src/utils/IdealGraphVisualizer/View/src/main/resources/com/sun/hotspot/igv/view/images/color.gif new file mode 100644 index 0000000000000..fb95a6f29589d Binary files /dev/null and b/src/utils/IdealGraphVisualizer/View/src/main/resources/com/sun/hotspot/igv/view/images/color.gif differ diff --git a/test/hotspot/jtreg/TEST.ROOT b/test/hotspot/jtreg/TEST.ROOT index 21c5aebaa716c..af97ff465def4 100644 --- a/test/hotspot/jtreg/TEST.ROOT +++ b/test/hotspot/jtreg/TEST.ROOT @@ -86,7 +86,9 @@ requires.properties= \ vm.flagless \ container.support \ systemd.support \ - jdk.containerized + jdk.containerized \ + jlink.runtime.linkable \ + jlink.packagedModules # Minimum jtreg version requiredVersion=7.4+1 diff --git a/test/hotspot/jtreg/compiler/c2/irTests/igvn/TestCombineAddPWithConstantOffsets.java b/test/hotspot/jtreg/compiler/c2/irTests/igvn/TestCombineAddPWithConstantOffsets.java new file mode 100644 index 0000000000000..69963316fa964 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/irTests/igvn/TestCombineAddPWithConstantOffsets.java @@ -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. + */ +package compiler.c2.irTests.igvn; + +import compiler.lib.ir_framework.*; + +/* + * @test + * @bug 8343067 + * @requires os.simpleArch == "x64" | os.simpleArch == "aarch64" + * @requires vm.compiler2.enabled + * @summary Test that chains of AddP nodes with constant offsets are idealized + * when their offset input changes. + * @library /test/lib / + * @run driver compiler.c2.irTests.igvn.TestCombineAddPWithConstantOffsets + */ +public class TestCombineAddPWithConstantOffsets { + + public static void main(String[] args) { + TestFramework.run(); + } + + @Test + @IR(applyIfPlatform = {"x64", "true"}, failOn = {IRNode.ADD_P_OF, ".*"}) + @IR(applyIfPlatform = {"aarch64", "true"}, failOn = {IRNode.ADD_P_OF, "reg_imm"}) + static void testCombineAddPWithConstantOffsets(int[] arr) { + for (long i = 6; i < 14; i++) { + arr[(int)i] = 1; + } + } + + @Run(test = {"testCombineAddPWithConstantOffsets"}) + public void runTests() { + testCombineAddPWithConstantOffsets(new int[14]); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index fc55662d8013f..1d586e972be3d 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -279,6 +279,12 @@ public class IRNode { superWordNodes(ADD_REDUCTION_VL, "AddReductionVL"); } + public static final String ADD_P_OF = COMPOSITE_PREFIX + "ADD_P_OF" + POSTFIX; + static { + String regex = START + "addP_" + IS_REPLACED + MID + ".*" + END; + machOnly(ADD_P_OF, regex); + } + public static final String ALLOC = PREFIX + "ALLOC" + POSTFIX; static { String optoRegex = "(.*precise .*\\R((.*(?i:mov|mv|xorl|nop|spill).*|\\s*)\\R)*.*(?i:call,static).*wrapper for: C2 Runtime new_instance" + END; diff --git a/test/hotspot/jtreg/compiler/patches/java.base/java/lang/Helper.java b/test/hotspot/jtreg/compiler/patches/java.base/java/lang/Helper.java index 5ecc01aa2bc29..a60354ec2fce2 100644 --- a/test/hotspot/jtreg/compiler/patches/java.base/java/lang/Helper.java +++ b/test/hotspot/jtreg/compiler/patches/java.base/java/lang/Helper.java @@ -23,6 +23,8 @@ package java.lang; +import jdk.internal.util.DecimalDigits; + /** * A helper class to get access to package-private members */ @@ -117,11 +119,17 @@ public static int codePointCountSB(byte[] val, int beginIndex, int endIndex) { } public static int getChars(int i, int begin, int end, byte[] value) { - return StringUTF16.getChars(i, begin, end, value); + StringUTF16.checkBoundsBeginEnd(begin, end, value); + int pos = DecimalDigits.getCharsUTF16(i, end, value); + assert begin == pos; + return pos; } public static int getChars(long l, int begin, int end, byte[] value) { - return StringUTF16.getChars(l, begin, end, value); + StringUTF16.checkBoundsBeginEnd(begin, end, value); + int pos = DecimalDigits.getCharsUTF16(l, end, value); + assert begin == pos; + return pos; } public static boolean contentEquals(byte[] v1, byte[] v2, int len) { diff --git a/test/jdk/TEST.ROOT b/test/jdk/TEST.ROOT index 6276932afbd14..7b6276c7aa058 100644 --- a/test/jdk/TEST.ROOT +++ b/test/jdk/TEST.ROOT @@ -102,7 +102,9 @@ requires.properties= \ systemd.support \ release.implementor \ jdk.containerized \ - jdk.foreign.linker + jdk.foreign.linker \ + jlink.runtime.linkable \ + jlink.packagedModules # Minimum jtreg version requiredVersion=7.4+1 diff --git a/test/jdk/com/sun/crypto/provider/Cipher/RSA/TestOAEPPadding.java b/test/jdk/com/sun/crypto/provider/Cipher/RSA/TestOAEPPadding.java index 2a82f2214d459..3bacff7617dc9 100644 --- a/test/jdk/com/sun/crypto/provider/Cipher/RSA/TestOAEPPadding.java +++ b/test/jdk/com/sun/crypto/provider/Cipher/RSA/TestOAEPPadding.java @@ -57,7 +57,7 @@ public static void main(String args[]) throws Exception { System.getProperty("test.provider.name", "SunJCE")); System.out.println("Testing provider " + cp.getName() + "..."); Provider kfp = Security.getProvider( - System.getProperty("test.providername", "SunRsaSign")); + System.getProperty("test.provider.name", "SunRsaSign")); String kpgAlgorithm = "RSA"; KeyPairGenerator kpg = KeyPairGenerator.getInstance(kpgAlgorithm, kfp); kpg.initialize(SecurityUtils.getTestKeySize(kpgAlgorithm)); diff --git a/test/jdk/java/awt/PrintJob/PrintCheckboxTest/PrintCheckboxManualTest.java b/test/jdk/java/awt/PrintJob/PrintCheckboxTest/PrintCheckboxManualTest.java index 4fd3ee8ef1691..72ad2a4589e30 100644 --- a/test/jdk/java/awt/PrintJob/PrintCheckboxTest/PrintCheckboxManualTest.java +++ b/test/jdk/java/awt/PrintJob/PrintCheckboxTest/PrintCheckboxManualTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2007, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 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,84 +22,52 @@ */ /* - @test - @bug 5045936 5055171 - @summary Tests that there is no ClassCastException thrown in printing - checkbox and scrollbar with XAWT - @key printer - @run applet/manual=yesno PrintCheckboxManualTest.html -*/ - -// Note there is no @ in front of test above. This is so that the -// harness will not mistake this file as a test file. It should -// only see the html file as a test file. (the harness runs all -// valid test files, so it would run this test twice if this file -// were valid as well as the html file.) -// Also, note the area= after Your Name in the author tag. Here, you -// should put which functional area the test falls in. See the -// AWT-core home page -> test areas and/or -> AWT team for a list of -// areas. - - - -import java.awt.*; -import java.awt.event.*; - - -//Manual tests should run as applet tests if possible because they -// get their environments cleaned up, including AWT threads, any -// test created threads, and any system resources used by the test -// such as file descriptors. (This is normally not a problem as -// main tests usually run in a separate VM, however on some platforms -// such as the Mac, separate VMs are not possible and non-applet -// tests will cause problems). Also, you don't have to worry about -// synchronisation stuff in Applet tests the way you do in main -// tests... - - -public class PrintCheckboxManualTest extends Panel -{ - //Declare things used in the test, like buttons and labels here - Frame f; - - public static void main(String[] args) { - PrintCheckboxManualTest a = new PrintCheckboxManualTest(); + * @test + * @bug 5045936 5055171 + * @summary Tests that there is no ClassCastException thrown in printing + * checkbox and scrollbar with XAWT + * @key printer + * @requires (os.family == "linux") + * @library /java/awt/regtesthelpers + * @build PassFailJFrame + * @run main/manual PrintCheckboxManualTest + */ - a.init(); - a.start(); +import java.awt.Button; +import java.awt.Checkbox; +import java.awt.Frame; +import java.awt.Graphics; +import java.awt.GridLayout; +import java.awt.Panel; +import java.awt.PrintJob; +import java.awt.Scrollbar; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class PrintCheckboxManualTest extends Panel { + + private static final String INSTRUCTIONS = """ + This test is for Linux with XToolkit ONLY!, + 1. Click the 'Print' button on the frame + 2. Select a printer in the print dialog and proceed + 3. If the frame with checkbox and button on it + is printed without any exception test PASSED else FAILED. + """; + + public static void main(String[] args) throws Exception { + PassFailJFrame.builder() + .title("Instructions") + .instructions(INSTRUCTIONS) + .columns(40) + .testUI(PrintCheckboxManualTest::createTestUI) + .build() + .awaitAndCheck(); } - public void init() - { - //Create instructions for the user here, as well as set up - // the environment -- set the layout manager, add buttons, - // etc. - this.setLayout (new BorderLayout ()); - - String[] instructions = - { - "Linux or Solaris with XToolkit ONLY!", - "1. Click the 'Print' button on the frame", - "2. Select a printer in the print dialog and proceed", - "3. If the frame with checkbox and button on it is printed successfully test PASSED else FAILED" - }; - Sysout.createDialogWithInstructions( instructions ); - - }//End init() + private static Frame createTestUI() { - public void start () - { - //Get things going. Request focus, set size, et cetera - setSize (200,200); - setVisible(true); - validate(); - - //What would normally go into main() will probably go here. - //Use System.out.println for diagnostic messages that you want - // to read after the test is done. - //Use Sysout.println for messages you want the tester to read. - - f = new Frame("Print checkbox"); + Frame f = new Frame("Print checkbox"); f.setLayout(new GridLayout(2, 2)); f.setSize(200, 100); @@ -111,185 +79,26 @@ public void start () f.add(sb); Button b = new Button("Print"); - b.addActionListener(new ActionListener() - { - public void actionPerformed(ActionEvent ev) - { - PrintJob pj = Toolkit.getDefaultToolkit().getPrintJob(f, "PrintCheckboxManualTest", null); - if (pj != null) - { - try - { - Graphics g = pj.getGraphics(); - f.printAll(g); - g.dispose(); - pj.end(); - Sysout.println("Test PASSED"); - } - catch (ClassCastException cce) - { - Sysout.println("Test FAILED: ClassCastException"); -// throw new RuntimeException("Test FAILED: ClassCastException", cce); - } - catch (Exception e) - { - Sysout.println("Test FAILED: unknown Exception"); -// throw new Error("Test FAILED: unknown exception", e); - } + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ev) { + PrintJob pj = Toolkit.getDefaultToolkit(). + getPrintJob(f, "PrintCheckboxManualTest", + null); + if (pj != null) { + try { + Graphics g = pj.getGraphics(); + f.printAll(g); + g.dispose(); + pj.end(); + } catch (ClassCastException cce) { + throw new RuntimeException("Test FAILED: ClassCastException", cce); + } catch (Exception e) { + throw new Error("Test FAILED: unknown exception", e); + } } - } + } }); f.add(b); - - f.setVisible(true); - }// start() - - //The rest of this class is the actions which perform the test... - - //Use Sysout.println to communicate with the user NOT System.out!! - //Sysout.println ("Something Happened!"); - -} - -/* Place other classes related to the test after this line */ - - - - - -/**************************************************** - Standard Test Machinery - DO NOT modify anything below -- it's a standard - chunk of code whose purpose is to make user - interaction uniform, and thereby make it simpler - to read and understand someone else's test. - ****************************************************/ - -/** - This is part of the standard test machinery. - It creates a dialog (with the instructions), and is the interface - for sending text messages to the user. - To print the instructions, send an array of strings to Sysout.createDialog - WithInstructions method. Put one line of instructions per array entry. - To display a message for the tester to see, simply call Sysout.println - with the string to be displayed. - This mimics System.out.println but works within the test harness as well - as standalone. - */ - -class Sysout -{ - private static TestDialog dialog; - - public static void createDialogWithInstructions( String[] instructions ) - { - dialog = new TestDialog( new Frame(), "Instructions" ); - dialog.printInstructions( instructions ); - dialog.setVisible(true); - println( "Any messages for the tester will display here." ); + return f; } - - public static void createDialog( ) - { - dialog = new TestDialog( new Frame(), "Instructions" ); - String[] defInstr = { "Instructions will appear here. ", "" } ; - dialog.printInstructions( defInstr ); - dialog.setVisible(true); - println( "Any messages for the tester will display here." ); - } - - - public static void printInstructions( String[] instructions ) - { - dialog.printInstructions( instructions ); - } - - - public static void println( String messageIn ) - { - dialog.displayMessage( messageIn ); - } - -}// Sysout class - -/** - This is part of the standard test machinery. It provides a place for the - test instructions to be displayed, and a place for interactive messages - to the user to be displayed. - To have the test instructions displayed, see Sysout. - To have a message to the user be displayed, see Sysout. - Do not call anything in this dialog directly. - */ -class TestDialog extends Dialog -{ - - TextArea instructionsText; - TextArea messageText; - int maxStringLength = 80; - - //DO NOT call this directly, go through Sysout - public TestDialog( Frame frame, String name ) - { - super( frame, name ); - int scrollBoth = TextArea.SCROLLBARS_BOTH; - instructionsText = new TextArea( "", 15, maxStringLength, scrollBoth ); - add( "North", instructionsText ); - - messageText = new TextArea( "", 5, maxStringLength, scrollBoth ); - add("Center", messageText); - - pack(); - - setVisible(true); - }// TestDialog() - - //DO NOT call this directly, go through Sysout - public void printInstructions( String[] instructions ) - { - //Clear out any current instructions - instructionsText.setText( "" ); - - //Go down array of instruction strings - - String printStr, remainingStr; - for( int i=0; i < instructions.length; i++ ) - { - //chop up each into pieces maxSringLength long - remainingStr = instructions[ i ]; - while( remainingStr.length() > 0 ) - { - //if longer than max then chop off first max chars to print - if( remainingStr.length() >= maxStringLength ) - { - //Try to chop on a word boundary - int posOfSpace = remainingStr. - lastIndexOf( ' ', maxStringLength - 1 ); - - if( posOfSpace <= 0 ) posOfSpace = maxStringLength - 1; - - printStr = remainingStr.substring( 0, posOfSpace + 1 ); - remainingStr = remainingStr.substring( posOfSpace + 1 ); - } - //else just print - else - { - printStr = remainingStr; - remainingStr = ""; - } - - instructionsText.append( printStr + "\n" ); - - }// while - - }// for - - }//printInstructions() - - //DO NOT call this directly, go through Sysout - public void displayMessage( String messageIn ) - { - messageText.append( messageIn + "\n" ); - System.out.println(messageIn); - } - -}// TestDialog class +} diff --git a/test/jdk/java/foreign/TestMemorySession.java b/test/jdk/java/foreign/TestMemorySession.java index e5b4ca74027f1..b06e2707c399d 100644 --- a/test/jdk/java/foreign/TestMemorySession.java +++ b/test/jdk/java/foreign/TestMemorySession.java @@ -326,52 +326,56 @@ public void testAcquireCloseRace() throws InterruptedException { int iteration = 1000; AtomicInteger lock = new AtomicInteger(); boolean[] result = new boolean[1]; - lock.set(-2); MemorySessionImpl[] scopes = new MemorySessionImpl[iteration]; for (int i = 0; i < iteration; i++) { scopes[i] = MemorySessionImpl.toMemorySession(Arena.ofShared()); } + // These two threads proceed the scopes array in a lock-step manner, the first thread wait + // for the second thread on the lock variable, while the second thread wait for the first + // thread on the closing of the current scope + // This thread tries to close the scopes Thread t1 = new Thread(() -> { - for (int i = 0; i < iteration; i++) { + for (int i = 0; i < iteration;) { MemorySessionImpl scope = scopes[i]; while (true) { try { scope.close(); + // Continue to the next iteration after a successful close break; - } catch (IllegalStateException e) {} + } catch (IllegalStateException e) { + // Wait for the release and try again + } } - // Keep the 2 threads operating on the same scope - int k = lock.getAndAdd(1) + 1; - while (k != i * 2) { + // Wait for the other thread to complete its iteration + int prev = i; + while (prev == i) { + i = lock.get(); Thread.onSpinWait(); - k = lock.get(); } } }); // This thread tries to acquire the scopes, then check if it is alive after an acquire failure Thread t2 = new Thread(() -> { - for (int i = 0; i < iteration; i++) { + for (int i = 0; i < iteration;) { MemorySessionImpl scope = scopes[i]; while (true) { try { scope.acquire0(); } catch (IllegalStateException e) { + // The scope has been closed, proceed to the next iteration if (scope.isAlive()) { result[0] = true; } break; } + // Release and try again scope.release0(); } - // Keep the 2 threads operating on the same scope - int k = lock.getAndAdd(1) + 1; - while (k != i * 2) { - Thread.onSpinWait(); - k = lock.get(); - } + // Proceed to the next iteration + i = lock.getAndAdd(1) + 1; } }); diff --git a/test/jdk/jdk/modules/etc/JmodExcludedFiles.java b/test/jdk/jdk/modules/etc/JmodExcludedFiles.java index 6c338a25b4f09..90ca6840d52a6 100644 --- a/test/jdk/jdk/modules/etc/JmodExcludedFiles.java +++ b/test/jdk/jdk/modules/etc/JmodExcludedFiles.java @@ -25,6 +25,7 @@ * @test * @bug 8159927 * @modules java.base/jdk.internal.util + * @requires jlink.packagedModules * @run main JmodExcludedFiles * @summary Test that JDK JMOD files do not include native debug symbols */ diff --git a/test/jdk/sun/security/provider/acvp/Launcher.java b/test/jdk/sun/security/provider/acvp/Launcher.java new file mode 100644 index 0000000000000..5a2ef1233f278 --- /dev/null +++ b/test/jdk/sun/security/provider/acvp/Launcher.java @@ -0,0 +1,162 @@ +/* + * 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. + */ +import jdk.test.lib.json.JSONValue; +import jtreg.SkippedException; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.Provider; +import java.security.Security; + +/* + * @test + * @bug 8342442 + * @library /test/lib + */ + +/// This test runs on `internalProjection.json`-style files generated +/// by NIST's ACVP Server. See [https://github.com/usnistgov/ACVP-Server]. +/// +/// The files are either put into the `data` directory or another +/// directory specified by the `test.acvp.data` test property. +/// The test walks through the directory recursively and looks for +/// file names equal to or ending with `internalProjection.json` and +/// runs tests on them. +/// +/// Set the `test.acvp.alg` test property to only test the specified algorithm. +/// +/// Sample files can be downloaded from +/// [https://github.com/usnistgov/ACVP-Server/tree/master/gen-val/json-files]. +/// +/// By default, the test uses system-preferred implementations. +/// If you want to test a specific provider, set the +/// `test.acvp.provider` test property. The provider must be +/// registered. +/// +/// Tests for each algorithm must be compliant to its specification linked from +/// [https://github.com/usnistgov/ACVP?tab=readme-ov-file#supported-algorithms]. +/// +/// Example: +/// ``` +/// jtreg -Dtest.acvp.provider=SunJCE \ +/// -Dtest.acvp.alg=ML-KEM \ +/// -Dtest.acvp.data=/path/to/json-files/ \ +/// -jdk:/path/to/jdk Launcher.java +/// ``` +public class Launcher { + + private static final String ONLY_ALG + = System.getProperty("test.acvp.alg"); + + private static final Provider PROVIDER; + + private static int count = 0; + private static int invalidTest = 0; + private static int unsupportedTest = 0; + + static { + var provProp = System.getProperty("test.acvp.provider"); + if (provProp != null) { + var p = Security.getProvider(provProp); + if (p == null) { + System.err.println(provProp + " is not a registered provider name"); + throw new RuntimeException(provProp + " is not a registered provider name"); + } + PROVIDER = p; + } else { + PROVIDER = null; + } + } + + public static void main(String[] args) throws Exception { + + var testDataProp = System.getProperty("test.acvp.data"); + Path dataPath = testDataProp != null + ? Path.of(testDataProp) + : Path.of(System.getProperty("test.src"), "data"); + System.out.println("Data path: " + dataPath); + + if (PROVIDER != null) { + System.out.println("Provider: " + PROVIDER.getName()); + } + if (ONLY_ALG != null) { + System.out.println("Algorithm: " + ONLY_ALG); + } + + try (var stream = Files.walk(dataPath)) { + stream.filter(Files::isRegularFile) + .filter(p -> p.getFileName().toString() + .endsWith("internalProjection.json")) + .forEach(Launcher::run); + } + + if (count > 0) { + System.out.println(); + System.out.println("Test completed: " + count); + System.out.println("Invalid tests: " + invalidTest); + System.out.println("Unsupported tests: " + unsupportedTest); + } else { + throw new SkippedException("No supported test found"); + } + } + + static void run(Path test) { + try { + JSONValue kat; + try { + kat = JSONValue.parse(Files.readString(test)); + } catch (Exception e) { + System.out.println("Warning: cannot parse " + test + ". Skipped"); + invalidTest++; + return; + } + var alg = kat.get("algorithm").asString(); + if (ONLY_ALG != null && !alg.equals(ONLY_ALG)) { + return; + } + System.out.println(">>> Testing " + test + "..."); + switch (alg) { + case "ML-DSA" -> { + ML_DSA_Test.run(kat, PROVIDER); + count++; + } + case "ML-KEM" -> { + ML_KEM_Test.run(kat, PROVIDER); + count++; + } + case "SHA2-256", "SHA2-224", "SHA3-256", "SHA3-224" -> { + SHA_Test.run(kat, PROVIDER); + count++; + } + default -> { + System.out.println("Skipped unsupported algorithm: " + alg); + unsupportedTest++; + } + } + } catch (RuntimeException re) { + throw re; + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/test/jdk/sun/security/provider/acvp/ML_DSA_Test.java b/test/jdk/sun/security/provider/acvp/ML_DSA_Test.java new file mode 100644 index 0000000000000..6d402516410bd --- /dev/null +++ b/test/jdk/sun/security/provider/acvp/ML_DSA_Test.java @@ -0,0 +1,126 @@ +/* + * 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. + */ +import jdk.test.lib.Asserts; +import jdk.test.lib.json.JSONValue; +import jdk.test.lib.security.FixedSecureRandom; + +import java.security.*; +import java.security.spec.EncodedKeySpec; +import java.security.spec.NamedParameterSpec; + +import static jdk.test.lib.Utils.toByteArray; + +// JSON spec at https://pages.nist.gov/ACVP/draft-celi-acvp-ml-dsa.html +public class ML_DSA_Test { + + public static void run(JSONValue kat, Provider provider) throws Exception { + var mode = kat.get("mode").asString(); + switch (mode) { + case "keyGen" -> keyGenTest(kat, provider); + case "sigGen" -> sigGenTest(kat, provider); + case "sigVer" -> sigVerTest(kat, provider); + default -> throw new UnsupportedOperationException("Unknown mode: " + mode); + } + } + + static void keyGenTest(JSONValue kat, Provider p) throws Exception { + var g = p == null + ? KeyPairGenerator.getInstance("ML-DSA") + : KeyPairGenerator.getInstance("ML-DSA", p); + var f = p == null + ? KeyFactory.getInstance("ML-DSA") + : KeyFactory.getInstance("ML-DSA", p); + for (var t : kat.get("testGroups").asArray()) { + var pname = t.get("parameterSet").asString(); + var np = new NamedParameterSpec(pname); + System.out.println(">> " + pname); + for (var c : t.get("tests").asArray()) { + System.out.print(c.get("tcId").asString() + " "); + g.initialize(np, new FixedSecureRandom(toByteArray(c.get("seed").asString()))); + var kp = g.generateKeyPair(); + var pk = f.getKeySpec(kp.getPublic(), EncodedKeySpec.class).getEncoded(); + var sk = f.getKeySpec(kp.getPrivate(), EncodedKeySpec.class).getEncoded(); + Asserts.assertEqualsByteArray(pk, toByteArray(c.get("pk").asString())); + Asserts.assertEqualsByteArray(sk, toByteArray(c.get("sk").asString())); + } + System.out.println(); + } + } + + static void sigGenTest(JSONValue kat, Provider p) throws Exception { + var s = p == null + ? Signature.getInstance("ML-DSA") + : Signature.getInstance("ML-DSA", p); + for (var t : kat.get("testGroups").asArray()) { + var pname = t.get("parameterSet").asString(); + var det = Boolean.parseBoolean(t.get("deterministic").asString()); + System.out.println(">> " + pname + " sign"); + for (var c : t.get("tests").asArray()) { + System.out.print(Integer.parseInt(c.get("tcId").asString()) + " "); + var sk = new PrivateKey() { + public String getAlgorithm() { return pname; } + public String getFormat() { return "RAW"; } + public byte[] getEncoded() { return toByteArray(c.get("sk").asString()); } + }; + var sr = new FixedSecureRandom( + det ? new byte[32] : toByteArray(c.get("rnd").asString())); + s.initSign(sk, sr); + s.update(toByteArray(c.get("message").asString())); + var sig = s.sign(); + Asserts.assertEqualsByteArray( + sig, toByteArray(c.get("signature").asString())); + } + System.out.println(); + } + } + + static void sigVerTest(JSONValue kat, Provider p) throws Exception { + var s = p == null + ? Signature.getInstance("ML-DSA") + : Signature.getInstance("ML-DSA", p); + for (var t : kat.get("testGroups").asArray()) { + var pname = t.get("parameterSet").asString(); + var pk = new PublicKey() { + public String getAlgorithm() { return pname; } + public String getFormat() { return "RAW"; } + public byte[] getEncoded() { return toByteArray(t.get("pk").asString()); } + }; + System.out.println(">> " + pname + " verify"); + for (var c : t.get("tests").asArray()) { + System.out.print(c.get("tcId").asString() + " "); + // Only ML-DSA sigVer has negative tests + var expected = Boolean.parseBoolean(c.get("testPassed").asString()); + var actual = true; + try { + s.initVerify(pk); + s.update(toByteArray(c.get("message").asString())); + actual = s.verify(toByteArray(c.get("signature").asString())); + } catch (InvalidKeyException | SignatureException e) { + actual = false; + } + Asserts.assertEQ(expected, actual); + } + System.out.println(); + } + } +} diff --git a/test/jdk/sun/security/provider/acvp/ML_KEM_Test.java b/test/jdk/sun/security/provider/acvp/ML_KEM_Test.java new file mode 100644 index 0000000000000..5b707bee6d8b0 --- /dev/null +++ b/test/jdk/sun/security/provider/acvp/ML_KEM_Test.java @@ -0,0 +1,112 @@ +/* + * 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. + */ +import jdk.test.lib.Asserts; +import jdk.test.lib.json.JSONValue; +import jdk.test.lib.security.FixedSecureRandom; + +import javax.crypto.KEM; +import java.security.*; +import java.security.spec.EncodedKeySpec; +import java.security.spec.NamedParameterSpec; + +import static jdk.test.lib.Utils.toByteArray; + +// JSON spec at https://pages.nist.gov/ACVP/draft-celi-acvp-ml-kem.html +public class ML_KEM_Test { + + public static void run(JSONValue kat, Provider provider) throws Exception { + var mode = kat.get("mode").asString(); + switch (mode) { + case "keyGen" -> keyGenTest(kat, provider); + case "encapDecap" -> encapDecapTest(kat, provider); + default -> throw new UnsupportedOperationException("Unknown mode: " + mode); + } + } + + static void keyGenTest(JSONValue kat, Provider p) throws Exception { + var g = p == null + ? KeyPairGenerator.getInstance("ML-KEM") + : KeyPairGenerator.getInstance("ML-KEM", p); + var f = p == null + ? KeyFactory.getInstance("ML-KEM") + : KeyFactory.getInstance("ML-KEM", p); + for (var t : kat.get("testGroups").asArray()) { + var pname = t.get("parameterSet").asString(); + var np = new NamedParameterSpec(pname); + System.out.println(">> " + pname); + for (var c : t.get("tests").asArray()) { + System.out.print(c.get("tcId").asString() + " "); + g.initialize(np, new FixedSecureRandom( + toByteArray(c.get("d").asString()), toByteArray(c.get("z").asString()))); + var kp = g.generateKeyPair(); + var pk = f.getKeySpec(kp.getPublic(), EncodedKeySpec.class).getEncoded(); + var sk = f.getKeySpec(kp.getPrivate(), EncodedKeySpec.class).getEncoded(); + Asserts.assertEqualsByteArray(pk, toByteArray(c.get("ek").asString())); + Asserts.assertEqualsByteArray(sk, toByteArray(c.get("dk").asString())); + } + System.out.println(); + } + } + + static void encapDecapTest(JSONValue kat, Provider p) throws Exception { + var g = p == null + ? KEM.getInstance("ML-KEM") + : KEM.getInstance("ML-KEM", p); + for (var t : kat.get("testGroups").asArray()) { + var pname = t.get("parameterSet").asString(); + var function = t.get("function").asString(); + System.out.println(">> " + pname + " " + function); + if (function.equals("encapsulation")) { + for (var c : t.get("tests").asArray()) { + System.out.print(c.get("tcId").asString() + " "); + var ek = new PublicKey() { + public String getAlgorithm() { return pname; } + public String getFormat() { return "RAW"; } + public byte[] getEncoded() { return toByteArray(c.get("ek").asString()); } + }; + var e = g.newEncapsulator( + ek, new FixedSecureRandom(toByteArray(c.get("m").asString()))); + var enc = e.encapsulate(); + Asserts.assertEqualsByteArray( + enc.encapsulation(), toByteArray(c.get("c").asString())); + Asserts.assertEqualsByteArray( + enc.key().getEncoded(), toByteArray(c.get("k").asString())); + } + System.out.println(); + } else if (function.equals("decapsulation")) { + var dk = new PrivateKey() { + public String getAlgorithm() { return pname; } + public String getFormat() { return "RAW"; } + public byte[] getEncoded() { return toByteArray(t.get("dk").asString()); } + }; + for (var c : t.get("tests").asArray()) { + System.out.print(c.get("tcId").asString() + " "); + var d = g.newDecapsulator(dk); + var k = d.decapsulate(toByteArray(c.get("c").asString())); + Asserts.assertEqualsByteArray(k.getEncoded(), toByteArray(c.get("k").asString())); + } + System.out.println(); + } + } + } +} diff --git a/test/jdk/sun/security/provider/acvp/SHA_Test.java b/test/jdk/sun/security/provider/acvp/SHA_Test.java new file mode 100644 index 0000000000000..3e5d4bb252149 --- /dev/null +++ b/test/jdk/sun/security/provider/acvp/SHA_Test.java @@ -0,0 +1,139 @@ +/* + * 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. + */ +import jdk.test.lib.Asserts; +import jdk.test.lib.json.JSONValue; + +import java.security.*; +import java.util.Arrays; + +import static jdk.test.lib.Utils.toByteArray; + +// JSON spec at https://pages.nist.gov/ACVP/draft-celi-acvp-sha.html +// and https://pages.nist.gov/ACVP/draft-celi-acvp-sha3.html +public class SHA_Test { + + public static void run(JSONValue kat, Provider provider) throws Exception { + var alg = kat.get("algorithm").asString(); + if (alg.startsWith("SHA2-")) alg = "SHA-" + alg.substring(5); + var md = provider == null ? MessageDigest.getInstance(alg) + : MessageDigest.getInstance(alg, provider); + for (var t : kat.get("testGroups").asArray()) { + var testType = t.get("testType").asString(); + switch (testType) { + case "AFT" -> { + for (var c : t.get("tests").asArray()) { + System.out.print(c.get("tcId").asString() + " "); + var msg = toByteArray(c.get("msg").asString()); + var len = Integer.parseInt(c.get("len").asString()); + if (msg.length * 8 == len) { + Asserts.assertEqualsByteArray(md.digest(msg), + toByteArray(c.get("md").asString())); + } else { + System.out.print("bits "); + } + } + } + case "MCT" -> { + var mctVersion = t.get("mctVersion").asString(); + var trunc = mctVersion.equals("alternate"); + for (var c : t.get("tests").asArray()) { + System.out.print(c.get("tcId").asString() + " "); + var SEED = toByteArray(c.get("msg").asString()); + var INITIAL_SEED_LENGTH = Integer.parseInt(c.get("len").asString()); + if (SEED.length * 8 == INITIAL_SEED_LENGTH) { + for (var r : c.get("resultsArray").asArray()) { + if (alg.startsWith("SHA3-")) { + var MD = SEED; + for (var i = 0; i < 1000; i++) { + if (trunc) { + MD = Arrays.copyOf(MD, INITIAL_SEED_LENGTH / 8); + } + MD = md.digest(MD); + } + Asserts.assertEqualsByteArray(MD, + toByteArray(r.get("md").asString())); + SEED = MD; + } else { + var A = SEED; + var B = SEED; + var C = SEED; + byte[] MD = null; + for (var i = 0; i < 1000; i++) { + var MSG = concat(A, B, C); + if (trunc) { + MSG = Arrays.copyOf(MSG, INITIAL_SEED_LENGTH / 8); + } + MD = md.digest(MSG); + A = B; + B = C; + C = MD; + } + Asserts.assertEqualsByteArray(MD, + toByteArray(r.get("md").asString())); + SEED = MD; + } + } + } else { + System.out.print("bits "); + } + } + } + case "LDT" -> { + for (var c : t.get("tests").asArray()) { + System.out.print(c.get("tcId").asString() + " "); + var lm = c.get("largeMsg"); + var ct = toByteArray(lm.get("content").asString()); + var flen = Long.parseLong(lm.get("fullLength").asString()); + var clen = Long.parseLong(lm.get("contentLength").asString()); + var cc = 0L; + while (cc < flen) { + md.update(ct); + cc += clen; + } + Asserts.assertEqualsByteArray(md.digest(), + toByteArray(c.get("md").asString())); + } + } + default -> throw new UnsupportedOperationException( + "Unknown testType: " + testType); + } + System.out.println(); + } + } + + ///////////// + + static byte[] concat(byte[]... input) { + var sum = 0; + for (var i : input) { + sum += i.length; + } + var out = new byte[sum]; + sum = 0; + for (var i : input) { + System.arraycopy(i, 0, out, sum, i.length); + sum += i.length; + } + return out; + } +} diff --git a/test/jdk/sun/security/provider/acvp/data/acvp.md b/test/jdk/sun/security/provider/acvp/data/acvp.md new file mode 100644 index 0000000000000..68e073b038fb0 --- /dev/null +++ b/test/jdk/sun/security/provider/acvp/data/acvp.md @@ -0,0 +1,9 @@ +# Automated Cryptographic Validation Test System Sample JSON files v1.1.0.36 + +## License + +NIST-developed software is provided by NIST as a public service. You may use, copy, and distribute copies of the software in any medium, provided that you keep intact this entire notice. You may improve, modify, and create derivative works of the software or any portion of the software, and you may copy and distribute such modifications or works. Modified works should carry a notice stating that you changed the software and should note the date and nature of any such change. Please explicitly acknowledge the National Institute of Standards and Technology as the source of the software. + +NIST-developed software is expressly provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED, IN FACT, OR ARISING BY OPERATION OF LAW, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND DATA ACCURACY. NIST NEITHER REPRESENTS NOR WARRANTS THAT THE OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR ERROR-FREE, OR THAT ANY DEFECTS WILL BE CORRECTED. NIST DOES NOT WARRANT OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF THE SOFTWARE OR THE RESULTS THEREOF, INCLUDING BUT NOT LIMITED TO THE CORRECTNESS, ACCURACY, RELIABILITY, OR USEFULNESS OF THE SOFTWARE. + +You are solely responsible for determining the appropriateness of using and distributing the software and you assume all risks associated with its use, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and the unavailability or interruption of operation. This software is not intended to be used in any situation where a failure could cause risk of injury or damage to property. The software developed by NIST employees is not subject to copyright protection within the United States. diff --git a/test/jdk/tools/jlink/ImageFileCreatorTest.java b/test/jdk/tools/jlink/ImageFileCreatorTest.java index 84f77340f42c1..b6466c6a4d91d 100644 --- a/test/jdk/tools/jlink/ImageFileCreatorTest.java +++ b/test/jdk/tools/jlink/ImageFileCreatorTest.java @@ -34,11 +34,12 @@ import java.util.List; import java.util.Set; import java.util.stream.Stream; + +import jdk.tools.jlink.builder.ImageBuilder; import jdk.tools.jlink.internal.Archive; +import jdk.tools.jlink.internal.ExecutableImage; import jdk.tools.jlink.internal.ImageFileCreator; import jdk.tools.jlink.internal.ImagePluginStack; -import jdk.tools.jlink.internal.ExecutableImage; -import jdk.tools.jlink.builder.ImageBuilder; import jdk.tools.jlink.plugin.ResourcePool; @@ -223,6 +224,6 @@ public void storeFiles(ResourcePool content) { ImagePluginStack stack = new ImagePluginStack(noopBuilder, Collections.emptyList(), null, false); - ImageFileCreator.create(archives, ByteOrder.nativeOrder(), stack); + ImageFileCreator.create(archives, ByteOrder.nativeOrder(), stack, false, null); } } diff --git a/test/jdk/tools/jlink/IntegrationTest.java b/test/jdk/tools/jlink/IntegrationTest.java index e85d8f0d98443..686dd194adafd 100644 --- a/test/jdk/tools/jlink/IntegrationTest.java +++ b/test/jdk/tools/jlink/IntegrationTest.java @@ -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 @@ -25,7 +25,6 @@ import java.io.FileReader; import java.io.IOException; import java.io.UncheckedIOException; -import java.nio.ByteOrder; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -38,20 +37,18 @@ import java.util.Properties; import java.util.Set; import java.util.function.Function; -import jdk.tools.jlink.internal.Jlink; -import jdk.tools.jlink.internal.JlinkTask; + import jdk.tools.jlink.builder.DefaultImageBuilder; -import jdk.tools.jlink.internal.Platform; -import jdk.tools.jlink.plugin.ResourcePool; -import jdk.tools.jlink.plugin.ResourcePoolBuilder; -import jdk.tools.jlink.plugin.Plugin; import jdk.tools.jlink.internal.ExecutableImage; +import jdk.tools.jlink.internal.Jlink; import jdk.tools.jlink.internal.Jlink.JlinkConfiguration; import jdk.tools.jlink.internal.Jlink.PluginsConfiguration; +import jdk.tools.jlink.internal.JlinkTask; +import jdk.tools.jlink.internal.Platform; import jdk.tools.jlink.internal.PostProcessor; -import jdk.tools.jlink.internal.plugins.DefaultCompressPlugin; -import jdk.tools.jlink.internal.plugins.DefaultStripDebugPlugin; - +import jdk.tools.jlink.plugin.Plugin; +import jdk.tools.jlink.plugin.ResourcePool; +import jdk.tools.jlink.plugin.ResourcePoolBuilder; import tests.Helper; import tests.JImageGenerator; @@ -74,8 +71,6 @@ */ public class IntegrationTest { - private static final List ordered = new ArrayList<>(); - public static class MyPostProcessor implements PostProcessor, Plugin { public static final String NAME = "mypostprocessor"; @@ -162,7 +157,7 @@ private static void test() throws Exception { limits.add("java.management"); JlinkConfiguration config = new Jlink.JlinkConfiguration(output, mods, - JlinkTask.newModuleFinder(modulePaths, limits, mods)); + JlinkTask.newModuleFinder(modulePaths, limits, mods), false, false, false); List lst = new ArrayList<>(); diff --git a/test/jdk/tools/jlink/JLinkDedupTestBatchSizeOne.java b/test/jdk/tools/jlink/JLinkDedupTestBatchSizeOne.java index 8b2dee1b45ad7..c7af8865a793b 100644 --- a/test/jdk/tools/jlink/JLinkDedupTestBatchSizeOne.java +++ b/test/jdk/tools/jlink/JLinkDedupTestBatchSizeOne.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,14 +21,16 @@ * questions. */ -import jdk.test.lib.compiler.CompilerUtils; -import tests.JImageGenerator; - import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import jdk.test.lib.compiler.CompilerUtils; +import jdk.tools.jlink.internal.LinkableRuntimeImage; +import tests.JImageGenerator; + + /* * @test * @summary Make sure that modules can be linked using jlink @@ -54,10 +56,6 @@ public class JLinkDedupTestBatchSizeOne { private static final Path SRC_DIR = Paths.get(TEST_SRC, "dedup", "src"); private static final Path MODS_DIR = Paths.get("mods"); - private static final String MODULE_PATH = - Paths.get(JAVA_HOME, "jmods").toString() + - File.pathSeparator + MODS_DIR.toString(); - // the names of the modules in this test private static String[] modules = new String[]{"m1", "m2", "m3", "m4"}; @@ -69,8 +67,13 @@ private static boolean hasJmods() { return true; } - public static void compileAll() throws Throwable { - if (!hasJmods()) return; + private static String modulePath(boolean linkableRuntime) { + return (linkableRuntime ? "" : (Paths.get(JAVA_HOME, "jmods").toString() + + File.pathSeparator)) + MODS_DIR.toString(); + } + + public static void compileAll(boolean linkableRuntime) throws Throwable { + if (!linkableRuntime && !hasJmods()) return; for (String mn : modules) { Path msrc = SRC_DIR.resolve(mn); @@ -80,11 +83,15 @@ public static void compileAll() throws Throwable { } public static void main(String[] args) throws Throwable { - compileAll(); + boolean linkableRuntime = LinkableRuntimeImage.isLinkableRuntime(); + System.out.println("Running test on " + + (linkableRuntime ? "enabled" : "disabled") + + " capability of linking from the run-time image."); + compileAll(linkableRuntime); Path image = Paths.get("bug8311591"); JImageGenerator.getJLinkTask() - .modulePath(MODULE_PATH) + .modulePath(modulePath(linkableRuntime)) .output(image.resolve("out-jlink-dedup")) .addMods("m1") .addMods("m2") diff --git a/test/jdk/tools/jlink/JLinkHelpCapabilityTest.java b/test/jdk/tools/jlink/JLinkHelpCapabilityTest.java new file mode 100644 index 0000000000000..50f570251d208 --- /dev/null +++ b/test/jdk/tools/jlink/JLinkHelpCapabilityTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. + */ + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.spi.ToolProvider; + +import jdk.tools.jlink.internal.LinkableRuntimeImage; + +/* + * @test + * @summary Test jlink --help for capability output + * @modules jdk.jlink/jdk.tools.jlink.internal + * @requires vm.compMode != "Xcomp" + * @run main/othervm -Duser.language=en JLinkHelpCapabilityTest + */ +public class JLinkHelpCapabilityTest { + static final ToolProvider JLINK_TOOL = ToolProvider.findFirst("jlink") + .orElseThrow(() -> + new RuntimeException("jlink tool not found") + ); + + public static void main(String[] args) throws Exception { + boolean runtimeLinkCap = LinkableRuntimeImage.isLinkableRuntime(); + String capabilities = String.format("Linking from run-time image %s", + runtimeLinkCap ? "enabled" : "disabled"); + { + // Verify capability in --help output + StringWriter writer = new StringWriter(); + PrintWriter pw = new PrintWriter(writer); + JLINK_TOOL.run(pw, pw, "--help"); + String output = writer.toString().trim(); + String lines[] = output.split("\n"); + String capabilitiesMsg = null; + boolean seenCap = false; + for (int i = 0; i < lines.length; i++) { + if (lines[i].startsWith("Capabilities:")) { + seenCap = true; + continue; // skip 'Capabilities:' + } + if (!seenCap) { + continue; + } else { + // Line after capabilities is the message we care about + capabilitiesMsg = lines[i].trim(); + break; + } + } + System.out.println("DEBUG: Capabilities:"); + System.out.println("DEBUG: " + capabilitiesMsg); + if (!capabilities.equals(capabilitiesMsg)) { + System.err.println(output); + throw new AssertionError("'--help': Capabilities mismatch. Expected: '" + + capabilities +"' but got '" + capabilitiesMsg + "'"); + } + } + } +} diff --git a/test/jdk/tools/jlink/multireleasejar/JLinkMRJavaBaseVersionTest.java b/test/jdk/tools/jlink/multireleasejar/JLinkMRJavaBaseVersionTest.java index 46912a68033dc..1fba932798d90 100644 --- a/test/jdk/tools/jlink/multireleasejar/JLinkMRJavaBaseVersionTest.java +++ b/test/jdk/tools/jlink/multireleasejar/JLinkMRJavaBaseVersionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 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 @@ -27,6 +27,7 @@ * @summary jlink should use the version from java.base.jmod to find modules * @bug 8185130 * @summary jlink should throw error if target image and current JDK versions don't match + * @requires jlink.packagedModules * @modules java.base/jdk.internal.module * @library /test/lib * @build jdk.test.lib.process.* CheckRuntimeVersion diff --git a/test/jdk/tools/jlink/plugins/GenerateJLIClassesPluginTest.java b/test/jdk/tools/jlink/plugins/GenerateJLIClassesPluginTest.java index 5d06288fb59b5..1acad93f5d357 100644 --- a/test/jdk/tools/jlink/plugins/GenerateJLIClassesPluginTest.java +++ b/test/jdk/tools/jlink/plugins/GenerateJLIClassesPluginTest.java @@ -21,6 +21,9 @@ * questions. */ +import static java.lang.constant.ConstantDescs.CD_Object; +import static java.lang.constant.ConstantDescs.CD_int; + import java.io.IOException; import java.lang.classfile.ClassFile; import java.lang.constant.MethodTypeDesc; @@ -32,16 +35,15 @@ import java.util.stream.Collectors; import org.testng.Assert; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import jdk.tools.jlink.internal.LinkableRuntimeImage; import tests.Helper; import tests.JImageGenerator; import tests.JImageValidator; import tests.Result; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; - -import static java.lang.constant.ConstantDescs.CD_Object; -import static java.lang.constant.ConstantDescs.CD_int; /* * @test @@ -63,12 +65,20 @@ public class GenerateJLIClassesPluginTest { @BeforeTest public static void setup() throws Exception { - helper = Helper.newHelper(); + boolean isLinkableRuntime = LinkableRuntimeImage.isLinkableRuntime(); + System.out.println("DEBUG: Tests run on " + + (isLinkableRuntime ? "enabled" : "disabled") + + " capability of linking from the run-time image."); + System.out.println("DEBUG: default module-path, 'jmods', " + + (Helper.jdkHasPackagedModules() ? "" : "NOT ") + + "present."); + helper = Helper.newHelper(isLinkableRuntime); if (helper == null) { + // In case of no linkable run-time image and also no packaged + // modules, helper will be null. System.err.println("Test not run"); return; } - helper.generateDefaultModules(); } @Test @@ -79,7 +89,6 @@ public static void testSpecies() throws IOException { String fileString = "[SPECIES_RESOLVE] java.lang.invoke.BoundMethodHandle$Species_" + species + " (salvaged)\n"; Files.write(baseFile, fileString.getBytes(Charset.defaultCharset())); Result result = JImageGenerator.getJLinkTask() - .modulePath(helper.defaultModulePath()) .output(helper.createNewImageDir("generate-jli-file")) .option("--generate-jli-classes=@" + baseFile.toString()) .addMods("java.base") @@ -105,7 +114,6 @@ public static void testInvalidSignatures() throws IOException { fileString = "[LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeVirtual L_L (success)\n"; Files.write(failFile, fileString.getBytes(Charset.defaultCharset())); Result result = JImageGenerator.getJLinkTask() - .modulePath(helper.defaultModulePath()) .output(helper.createNewImageDir("invalid-signature")) .option("--generate-jli-classes=@" + failFile.toString()) .addMods("java.base") @@ -118,7 +126,6 @@ public static void testInvalidSignatures() throws IOException { @Test public static void nonExistentTraceFile() throws IOException { Result result = JImageGenerator.getJLinkTask() - .modulePath(helper.defaultModulePath()) .output(helper.createNewImageDir("non-existent-tracefile")) .option("--generate-jli-classes=@NON_EXISTENT_FILE") .addMods("java.base") @@ -134,7 +141,6 @@ public static void testInvokers() throws IOException { Path invokersTrace = Files.createTempFile("invokers", "trace"); Files.writeString(invokersTrace, fileString, Charset.defaultCharset()); Result result = JImageGenerator.getJLinkTask() - .modulePath(helper.defaultModulePath()) .output(helper.createNewImageDir("jli-invokers")) .option("--generate-jli-classes=@" + invokersTrace.toString()) .addMods("java.base") @@ -183,4 +189,5 @@ private static List classFilesForSpecies(Collection species) { .map(s -> "/java.base/java/lang/invoke/BoundMethodHandle$Species_" + s + ".class") .collect(Collectors.toList()); } + } diff --git a/test/jdk/tools/jlink/plugins/IncludeLocalesPluginTest.java b/test/jdk/tools/jlink/plugins/IncludeLocalesPluginTest.java index 2a0c43d3168e0..e2dd3403c83f5 100644 --- a/test/jdk/tools/jlink/plugins/IncludeLocalesPluginTest.java +++ b/test/jdk/tools/jlink/plugins/IncludeLocalesPluginTest.java @@ -22,25 +22,27 @@ */ import java.nio.file.Path; -import java.util.Arrays; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.stream.Collectors; -import jdk.tools.jlink.plugin.PluginException; +import jdk.tools.jlink.internal.LinkableRuntimeImage; import jdk.tools.jlink.internal.TaskHelper; import jdk.tools.jlink.internal.plugins.PluginsResourceBundle; +import jdk.tools.jlink.plugin.PluginException; import tests.Helper; import tests.JImageGenerator; import tests.JImageValidator; import tests.Result; + /* * @test * @bug 8152143 8152704 8155649 8165804 8185841 8176841 8190918 * 8179071 8202537 8221432 8222098 8251317 8258794 8265315 - * 8296248 8306116 8174269 8333582 + * 8296248 8306116 8174269 * @summary IncludeLocalesPlugin tests * @author Naoto Sato * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) @@ -59,14 +61,14 @@ */ public class IncludeLocalesPluginTest { - private final static String moduleName = "IncludeLocalesTest"; + private static final String moduleName = "IncludeLocalesTest"; private static Helper helper; - private final static int INCLUDE_LOCALES_OPTION = 0; - private final static int ADDMODS_OPTION = 1; - private final static int EXPECTED_LOCATIONS = 2; - private final static int UNEXPECTED_PATHS = 3; - private final static int AVAILABLE_LOCALES = 4; - private final static int ERROR_MESSAGE = 5; + private static final int INCLUDE_LOCALES_OPTION = 0; + private static final int ADDMODS_OPTION = 1; + private static final int EXPECTED_LOCATIONS = 2; + private static final int UNEXPECTED_PATHS = 3; + private static final int AVAILABLE_LOCALES = 4; + private static final int ERROR_MESSAGE = 5; private static int errors; @@ -413,11 +415,18 @@ public class IncludeLocalesPluginTest { }; public static void main(String[] args) throws Exception { - helper = Helper.newHelper(); + boolean isLinkableRuntime = LinkableRuntimeImage.isLinkableRuntime(); + System.out.println("Running test on " + + (isLinkableRuntime ? "enabled" : "disabled") + + " capability of linking from the run-time image."); + System.out.println("Default module-path, 'jmods', " + + (Helper.jdkHasPackagedModules() ? "" : "NOT ") + + "present."); + + helper = Helper.newHelper(isLinkableRuntime); if (helper == null) { throw new RuntimeException("Helper could not be initialized"); } - helper.generateDefaultModules(); for (Object[] data : testData) { // create image for each test data @@ -425,14 +434,12 @@ public static void main(String[] args) throws Exception { if (data[INCLUDE_LOCALES_OPTION].toString().isEmpty()) { System.out.println("Invoking jlink with no --include-locales option"); result = JImageGenerator.getJLinkTask() - .modulePath(helper.defaultModulePath()) .output(helper.createNewImageDir(moduleName)) .addMods((String) data[ADDMODS_OPTION]) .call(); } else { System.out.println("Invoking jlink with \"" + data[INCLUDE_LOCALES_OPTION] + "\""); result = JImageGenerator.getJLinkTask() - .modulePath(helper.defaultModulePath()) .output(helper.createNewImageDir(moduleName)) .addMods((String) data[ADDMODS_OPTION]) .option((String) data[INCLUDE_LOCALES_OPTION]) diff --git a/test/jdk/tools/jlink/runtimeImage/AbstractLinkableRuntimeTest.java b/test/jdk/tools/jlink/runtimeImage/AbstractLinkableRuntimeTest.java new file mode 100644 index 0000000000000..e7d5340e3b00a --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/AbstractLinkableRuntimeTest.java @@ -0,0 +1,705 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. + */ + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Scanner; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.tools.jlink.internal.LinkableRuntimeImage; +import tests.Helper; +import tests.JImageGenerator; +import tests.JImageGenerator.JLinkTask; +import tests.JImageValidator; + +public abstract class AbstractLinkableRuntimeTest { + + protected static final boolean DEBUG = true; + + public void run() throws Exception { + boolean isLinkableRuntime = LinkableRuntimeImage.isLinkableRuntime(); + Helper helper = Helper.newHelper(isLinkableRuntime); + if (helper == null) { + System.err.println(AbstractLinkableRuntimeTest.class.getSimpleName() + + ": Test not run"); + return; + } + runTest(helper, isLinkableRuntime); + System.out.println(getClass().getSimpleName() + " PASSED!"); + } + + /** + * Main test entry point that actual tests ought to override. + * + * @param helper The jlink helper + * @param isLinkableRuntime {@code true} iff the JDK build under test already + * includes the linkable runtime capability in jlink. + * @throws Exception + */ + abstract void runTest(Helper helper, boolean isLinkableRuntime) throws Exception; + + /** + * Ensure 'java --list-modules' lists the correct set of modules in the given + * image. + * + * @param jlinkImage + * @param expectedModules + */ + protected void verifyListModules(Path image, + List expectedModules) throws Exception { + OutputAnalyzer out = runJavaCmd(image, List.of("--list-modules")); + List actual = parseListMods(out.getStdout()); + Collections.sort(actual); + if (!expectedModules.equals(actual)) { + throw new AssertionError("Different modules! Expected " + expectedModules + " got: " + actual); + } + } + + protected OutputAnalyzer runJavaCmd(Path image, List options) throws Exception { + Path targetJava = image.resolve("bin").resolve(getJava()); + List cmd = new ArrayList<>(); + cmd.add(targetJava.toString()); + for (String opt: options) { + cmd.add(opt); + } + List javaCmd = Collections.unmodifiableList(cmd); + OutputAnalyzer out; + try { + out = ProcessTools.executeCommand(javaCmd.toArray(new String[0])); + } catch (Throwable e) { + throw new Exception("Process failed to execute", e); + } + if (out.getExitValue() != 0) { + if (DEBUG) { + System.err.println("Process stdout was: "); + System.err.println(out.getStdout()); + System.err.println("Process stderr was: "); + System.err.println(out.getStderr()); + } + throw new AssertionError("'" + javaCmd.stream().collect(Collectors.joining(" ")) + "'" + + " expected to succeed!"); + } + return out; + } + + protected Path createJavaImageRuntimeLink(BaseJlinkSpec baseSpec) throws Exception { + return createJavaImageRuntimeLink(baseSpec, Collections.emptySet() /* exclude all jmods */); + } + + protected Path createJavaImageRuntimeLink(BaseJlinkSpec baseSpec, Set excludedJmods) throws Exception { + // Be sure we have a JDK without JMODs + Path runtimeJlinkImage = createRuntimeLinkImage(baseSpec, excludedJmods); + + // On Windows jvm.dll is in 'bin' after the jlink + Path libjvm = Path.of((isWindows() ? "bin" : "lib"), "server", System.mapLibraryName("jvm")); + JlinkSpecBuilder builder = new JlinkSpecBuilder(); + // And expect libjvm (not part of the jimage) to be present in the resulting image + builder.expectedFile(libjvm.toString()) + .helper(baseSpec.getHelper()) + .name(baseSpec.getName()) + .validatingModule(baseSpec.getValidatingModule()) + .imagePath(runtimeJlinkImage) + .expectedLocation("/java.base/java/lang/String.class"); + for (String m: baseSpec.getModules()) { + builder.addModule(m); + } + for (String extra: baseSpec.getExtraOptions()) { + builder.extraJlinkOpt(extra); + } + return jlinkUsingImage(builder.build()); + } + + protected Path jlinkUsingImage(JlinkSpec spec) throws Exception { + return jlinkUsingImage(spec, new RuntimeLinkOutputAnalyzerHandler()); + } + + protected Path jlinkUsingImage(JlinkSpec spec, OutputAnalyzerHandler handler) throws Exception { + return jlinkUsingImage(spec, handler, new DefaultSuccessExitPredicate()); + } + + protected Path jlinkUsingImage(JlinkSpec spec, OutputAnalyzerHandler handler, Predicate exitChecker) throws Exception { + String generatedImage = "target-run-time-" + spec.getName(); + Path targetImageDir = spec.getHelper().createNewImageDir(generatedImage); + Path targetJlink = spec.getImageToUse().resolve("bin").resolve(getJlink()); + String[] jlinkCmdArray = new String[] { + targetJlink.toString(), + "--output", targetImageDir.toString(), + "--verbose", + "--add-modules", spec.getModules().stream().collect(Collectors.joining(",")) + }; + List jlinkCmd = new ArrayList<>(); + jlinkCmd.addAll(Arrays.asList(jlinkCmdArray)); + if (spec.getExtraJlinkOpts() != null && !spec.getExtraJlinkOpts().isEmpty()) { + jlinkCmd.addAll(spec.getExtraJlinkOpts()); + } + if (spec.getModulePath() != null) { + for (String mp: spec.getModulePath()) { + jlinkCmd.add("--module-path"); + jlinkCmd.add(mp); + } + } + jlinkCmd = Collections.unmodifiableList(jlinkCmd); // freeze + System.out.println("DEBUG: run-time image based jlink command: " + + jlinkCmd.stream().collect(Collectors.joining(" "))); + OutputAnalyzer analyzer = null; + try { + analyzer = ProcessTools.executeProcess(jlinkCmd.toArray(new String[0])); + } catch (Throwable t) { + throw new AssertionError("Executing process failed!", t); + } + if (!exitChecker.test(analyzer)) { + if (DEBUG) { + System.err.println("Process stdout was: "); + System.err.println(analyzer.getStdout()); + System.err.println("Process stderr was: "); + System.err.println(analyzer.getStderr()); + } + // if the exit checker failed, we expected the other outcome + // i.e. fail for success and success for fail. + boolean successExit = analyzer.getExitValue() == 0; + String msg = String.format("Expected jlink to %s given a jmodless image. Exit code was: %d", + (successExit ? "fail" : "pass"), analyzer.getExitValue()); + throw new AssertionError(msg); + } + handler.handleAnalyzer(analyzer); // Give tests a chance to process in/output + + // validate the resulting image; Includes running 'java -version', only do this + // if the jlink succeeded. + if (analyzer.getExitValue() == 0) { + JImageValidator validator = new JImageValidator(spec.getValidatingModule(), spec.getExpectedLocations(), + targetImageDir.toFile(), spec.getUnexpectedLocations(), Collections.emptyList(), spec.getExpectedFiles()); + validator.validate(); // This doesn't validate locations + if (!spec.getExpectedLocations().isEmpty() || !spec.getUnexpectedLocations().isEmpty()) { + JImageValidator.validate(targetImageDir.resolve("lib").resolve("modules"), spec.getExpectedLocations(), spec.getUnexpectedLocations()); + } + } + return targetImageDir; + } + + /** + * Prepares the test for execution. This assumes the current runtime + * supports linking from it. However, since the 'jmods' dir might be present + * (default jmods module path), the 'jmods' directory needs to get removed + * to provoke actual linking from the run-time image. + * + * @param baseSpec + * @return A path to a JDK that is capable for linking from the run-time + * image. + * @throws Exception + */ + protected Path createRuntimeLinkImage(BaseJlinkSpec baseSpec) throws Exception { + return createRuntimeLinkImage(baseSpec, Collections.emptySet() /* exclude all jmods */); + } + + /** + * Prepares the test for execution. Creates a JDK with a jlink that has the + * capability to link from the run-time image (if needed). It further + * ensures that if packaged modules ('jmods' dir) are present, to remove + * them entirely or as specified in the {@link excludedJmodFiles} set. If + * that set is empty, all packaged modules will be removed. Note that with + * packaged modules present no run-time image based linking would be done. + * + * @param baseSpec + * The specification for the custom - run-time image link capable + * - JDK to create via jlink (if any) + * @param excludedJmods + * The set of jmod files to exclude in the base JDK. Empty set if + * all JMODs should be removed. + * @return A path to a JDK, including jdk.jlink, that has the run-time image + * link capability. + * + * @throws Exception + */ + protected Path createRuntimeLinkImage(BaseJlinkSpec baseSpec, + Set excludedJmodFiles) throws Exception { + // Depending on the shape of the JDK under test, we either only filter + // jmod files or create a run-time image link capable JDK on-the-fly. + Path from = null; + Path runtimeJlinkImage = null; + String finalName = baseSpec.getName() + "-jlink"; + if (baseSpec.isLinkableRuntime()) { + // The build is already run-time image link capable + String javaHome = System.getProperty("java.home"); + from = Path.of(javaHome); + } else { + // Create a run-time image capable JDK using --generate-linkable-runtime + Path tempRuntimeImage = Path.of(finalName + "-tmp"); + JLinkTask task = JImageGenerator.getJLinkTask(); + task.output(tempRuntimeImage) + .addMods("jdk.jlink") // jdk.jlink module is always needed for the test + .option("--generate-linkable-runtime"); + if (baseJDKhasPackagedModules()) { + Path jmodsPath = tempRuntimeImage.resolve("jmods"); + task.option("--keep-packaged-modules=" + jmodsPath); + } + for (String module: baseSpec.getModules()) { + task.addMods(module); + } + task.call().assertSuccess(); + from = tempRuntimeImage; + } + + // Create the target directory + runtimeJlinkImage = baseSpec.getHelper().createNewImageDir(finalName); + + // Remove JMODs as needed for the test + copyJDKTreeWithoutSpecificJmods(from, runtimeJlinkImage, excludedJmodFiles); + // Verify the base image is actually without desired packaged modules + if (excludedJmodFiles.isEmpty()) { + if (Files.exists(runtimeJlinkImage.resolve("jmods"))) { + throw new AssertionError("Must not contain 'jmods' directory"); + } + } else { + Path basePath = runtimeJlinkImage.resolve("jmods"); + for (String jmodFile: excludedJmodFiles) { + Path unexpectedFile = basePath.resolve(Path.of(jmodFile)); + if (Files.exists(unexpectedFile)) { + throw new AssertionError("Must not contain jmod: " + unexpectedFile); + } + } + } + return runtimeJlinkImage; + } + + private boolean baseJDKhasPackagedModules() { + Path jmodsPath = Path.of(System.getProperty("java.home"), "jmods"); + return jmodsPath.toFile().exists(); + } + + private void copyJDKTreeWithoutSpecificJmods(Path from, + Path to, + Set excludedJmods) throws Exception { + if (Files.exists(to)) { + throw new AssertionError("Expected target dir '" + to + "' to exist"); + } + FileVisitor fileVisitor = null; + if (excludedJmods.isEmpty()) { + fileVisitor = new ExcludeAllJmodsFileVisitor(from, to); + } else { + fileVisitor = new FileExcludingFileVisitor(excludedJmods, + from, + to); + } + Files.walkFileTree(from, fileVisitor); + } + + private List parseListMods(String output) throws Exception { + List outputLines = new ArrayList<>(); + try (Scanner lineScan = new Scanner(output)) { + while (lineScan.hasNextLine()) { + outputLines.add(lineScan.nextLine()); + } + } + return outputLines.stream() + .map(a -> { return a.split("@", 2)[0];}) + .filter(a -> !a.isBlank()) + .collect(Collectors.toList()); + } + + private String getJlink() { + return getBinary("jlink"); + } + + private String getJava() { + return getBinary("java"); + } + + private String getBinary(String binary) { + return isWindows() ? binary + ".exe" : binary; + } + + protected static boolean isWindows() { + return System.getProperty("os.name").startsWith("Windows"); + } + + static class ExcludeAllJmodsFileVisitor extends SimpleFileVisitor { + private final Path root; + private final Path destination; + + private ExcludeAllJmodsFileVisitor(Path root, + Path destination) { + this.destination = destination; + this.root = root; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, + BasicFileAttributes attrs) throws IOException { + Objects.requireNonNull(dir); + Path relative = root.relativize(dir); + if (relative.getFileName().equals(Path.of("jmods"))) { + return FileVisitResult.SKIP_SUBTREE; + } + // Create dir in destination location + Path targetDir = destination.resolve(relative); + if (!Files.exists(targetDir)) { + Files.createDirectory(targetDir); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + Path relative = root.relativize(file); + Files.copy(file, destination.resolve(relative), StandardCopyOption.REPLACE_EXISTING); + return FileVisitResult.CONTINUE; + } + } + + static class FileExcludingFileVisitor extends SimpleFileVisitor { + + private final Set filesToExclude; + private final Path root; + private final Path destination; + + private FileExcludingFileVisitor(Set filesToExclude, + Path root, + Path destination) { + this.filesToExclude = filesToExclude; + this.destination = destination; + this.root = root; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, + BasicFileAttributes attrs) throws IOException { + Objects.requireNonNull(dir); + Path relative = root.relativize(dir); + // Create dir in destination location + Path targetDir = destination.resolve(relative); + if (!Files.exists(targetDir)) { + Files.createDirectory(targetDir); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + Path relative = root.relativize(file); + // Skip files as determined by the exclude set + String fileName = file.getFileName().toString(); + if (!filesToExclude.contains(fileName)) { + Files.copy(file, destination.resolve(relative), StandardCopyOption.REPLACE_EXISTING); + } + return FileVisitResult.CONTINUE; + } + + } + + static class BaseJlinkSpec { + final Helper helper; + final String name; + final String validatingModule; + final List modules; + final List extraOptions; + final boolean isLinkableRuntime; + + BaseJlinkSpec(Helper helper, String name, String validatingModule, + List modules, List extraOptions, boolean isLinkableRuntime) { + this.helper = helper; + this.name = name; + this.modules = modules; + this.extraOptions = extraOptions; + this.validatingModule = validatingModule; + this.isLinkableRuntime = isLinkableRuntime; + } + + public String getValidatingModule() { + return validatingModule; + } + + public Helper getHelper() { + return helper; + } + + public String getName() { + return name; + } + + public List getModules() { + return modules; + } + + public List getExtraOptions() { + return extraOptions; + } + + public boolean isLinkableRuntime() { + return isLinkableRuntime; + } + } + + static class BaseJlinkSpecBuilder { + Helper helper; + String name; + String validatingModule; + List modules = new ArrayList<>(); + List extraOptions = new ArrayList<>(); + boolean isLinkableRuntime; + + BaseJlinkSpecBuilder addModule(String module) { + modules.add(module); + return this; + } + + BaseJlinkSpecBuilder addExtraOption(String option) { + extraOptions.add(option); + return this; + } + + BaseJlinkSpecBuilder setLinkableRuntime() { + isLinkableRuntime = true; + return this; + } + + BaseJlinkSpecBuilder helper(Helper helper) { + this.helper = helper; + return this; + } + + BaseJlinkSpecBuilder name(String name) { + this.name = name; + return this; + } + + BaseJlinkSpecBuilder validatingModule(String module) { + this.validatingModule = module; + return this; + } + + BaseJlinkSpec build() { + if (name == null) { + throw new IllegalStateException("Name must be set"); + } + if (helper == null) { + throw new IllegalStateException("helper must be set"); + } + if (modules.isEmpty()) { + throw new IllegalStateException("modules must be set"); + } + if (validatingModule == null) { + throw new IllegalStateException("the module which should get validated must be set"); + } + return new BaseJlinkSpec(helper, name, validatingModule, modules, extraOptions, isLinkableRuntime); + } + } + + static class JlinkSpec { + final Path imageToUse; + final Helper helper; + final String name; + final List modules; + final String validatingModule; + final List expectedLocations; + final List unexpectedLocations; + final String[] expectedFiles; + final List extraJlinkOpts; + final List modulePath; + + JlinkSpec(Path imageToUse, Helper helper, String name, List modules, + String validatingModule, List expectedLocations, + List unexpectedLocations, String[] expectedFiles, + List extraJlinkOpts, + List modulePath) { + this.imageToUse = imageToUse; + this.helper = helper; + this.name = name; + this.modules = modules; + this.validatingModule = validatingModule; + this.expectedLocations = expectedLocations; + this.unexpectedLocations = unexpectedLocations; + this.expectedFiles = expectedFiles; + this.extraJlinkOpts = extraJlinkOpts; + this.modulePath = modulePath; + } + + public Path getImageToUse() { + return imageToUse; + } + + public Helper getHelper() { + return helper; + } + + public String getName() { + return name; + } + + public List getModules() { + return modules; + } + + public String getValidatingModule() { + return validatingModule; + } + + public List getExpectedLocations() { + return expectedLocations; + } + + public List getUnexpectedLocations() { + return unexpectedLocations; + } + + public String[] getExpectedFiles() { + return expectedFiles; + } + + public List getExtraJlinkOpts() { + return extraJlinkOpts; + } + + public List getModulePath() { + return modulePath; + } + } + + static class JlinkSpecBuilder { + Path imageToUse; + Helper helper; + String name; + List modules = new ArrayList<>(); + String validatingModule; + List expectedLocations = new ArrayList<>(); + List unexpectedLocations = new ArrayList<>(); + List expectedFiles = new ArrayList<>(); + List extraJlinkOpts = new ArrayList<>(); + List modulePath = new ArrayList<>(); + + JlinkSpec build() { + if (imageToUse == null) { + throw new IllegalStateException("No image to use for jlink specified!"); + } + if (helper == null) { + throw new IllegalStateException("No helper specified!"); + } + if (name == null) { + throw new IllegalStateException("No name for the image location specified!"); + } + if (validatingModule == null) { + throw new IllegalStateException("No module specified for after generation validation!"); + } + return new JlinkSpec(imageToUse, + helper, + name, + modules, + validatingModule, + expectedLocations, + unexpectedLocations, + expectedFiles.toArray(new String[0]), + extraJlinkOpts, + modulePath); + } + + JlinkSpecBuilder imagePath(Path image) { + this.imageToUse = image; + return this; + } + + JlinkSpecBuilder helper(Helper helper) { + this.helper = helper; + return this; + } + + JlinkSpecBuilder name(String name) { + this.name = name; + return this; + } + + JlinkSpecBuilder addModule(String module) { + modules.add(module); + return this; + } + + JlinkSpecBuilder validatingModule(String module) { + this.validatingModule = module; + return this; + } + + JlinkSpecBuilder addModulePath(String modulePath) { + this.modulePath.add(modulePath); + return this; + } + + JlinkSpecBuilder expectedLocation(String location) { + expectedLocations.add(location); + return this; + } + + JlinkSpecBuilder unexpectedLocation(String location) { + unexpectedLocations.add(location); + return this; + } + + JlinkSpecBuilder expectedFile(String file) { + expectedFiles.add(file); + return this; + } + + JlinkSpecBuilder extraJlinkOpt(String opt) { + extraJlinkOpts.add(opt); + return this; + } + } + + static abstract class OutputAnalyzerHandler { + + public abstract void handleAnalyzer(OutputAnalyzer out); + + } + + static class RuntimeLinkOutputAnalyzerHandler extends OutputAnalyzerHandler { + + @Override + public void handleAnalyzer(OutputAnalyzer out) { + out.shouldContain("Linking based on the current run-time image"); + } + + } + + static class DefaultSuccessExitPredicate implements Predicate { + + @Override + public boolean test(OutputAnalyzer t) { + return t.getExitValue() == 0; + } + + } +} diff --git a/test/jdk/tools/jlink/runtimeImage/AddOptionsTest.java b/test/jdk/tools/jlink/runtimeImage/AddOptionsTest.java new file mode 100644 index 0000000000000..1ffe1240d07e7 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/AddOptionsTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. + */ + +import java.nio.file.Path; +import java.util.List; +import java.util.Scanner; + +import jdk.test.lib.process.OutputAnalyzer; +import tests.Helper; + +/* + * @test + * @summary Test --add-options jlink plugin when linking from the run-time image + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g AddOptionsTest + */ +public class AddOptionsTest extends AbstractLinkableRuntimeTest { + + public static void main(String[] args) throws Exception { + AddOptionsTest test = new AddOptionsTest(); + test.run(); + } + + @Override + void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder() + .addExtraOption("--add-options") + .addExtraOption("-Xlog:gc=info:stderr -XX:+UseParallelGC") + .name("java-base-with-opts") + .addModule("java.base") + .validatingModule("java.base") + .helper(helper); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + Path finalImage = createJavaImageRuntimeLink(builder.build()); + verifyListModules(finalImage, List.of("java.base")); + verifyParallelGCInUse(finalImage); + } + + private void verifyParallelGCInUse(Path finalImage) throws Exception { + OutputAnalyzer analyzer = runJavaCmd(finalImage, List.of("--version")); + boolean foundMatch = false; + try (Scanner lineScan = new Scanner(analyzer.getStderr())) { + while (lineScan.hasNextLine()) { + String line = lineScan.nextLine(); + if (line.endsWith("Using Parallel")) { + foundMatch = true; + break; + } + } + } + if (!foundMatch) { + throw new AssertionError("Expected Parallel GC in place for jlinked image"); + } + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/BasicJlinkMissingJavaBase.java b/test/jdk/tools/jlink/runtimeImage/BasicJlinkMissingJavaBase.java new file mode 100644 index 0000000000000..b0d2a2d66f5b3 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/BasicJlinkMissingJavaBase.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. + */ + +import java.nio.file.Path; +import java.util.List; +import java.util.Set; + +import tests.Helper; + + +/* + * @test + * @summary Test basic linking from the run-time image with java.base.jmod missing + * but java.xml.jmod present. It should link from the run-time image without errors. + * @requires (jlink.packagedModules & vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g BasicJlinkMissingJavaBase + */ +public class BasicJlinkMissingJavaBase extends AbstractLinkableRuntimeTest { + + @Override + public void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + Path finalImage = createJavaXMLRuntimeLink(helper, "java-xml", isLinkableRuntime); + verifyListModules(finalImage, List.of("java.base", "java.xml")); + } + + private Path createJavaXMLRuntimeLink(Helper helper, String name, boolean isLinkableRuntime) throws Exception { + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder(); + builder.helper(helper) + .name(name) + .addModule("java.xml") + .validatingModule("java.xml"); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + Set excludedJmods = Set.of("java.base.jmod"); + return createJavaImageRuntimeLink(builder.build(), excludedJmods); + } + + public static void main(String[] args) throws Exception { + BasicJlinkMissingJavaBase test = new BasicJlinkMissingJavaBase(); + test.run(); + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/BasicJlinkTest.java b/test/jdk/tools/jlink/runtimeImage/BasicJlinkTest.java new file mode 100644 index 0000000000000..b97ebff9b4906 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/BasicJlinkTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. + */ + +import java.nio.file.Path; +import java.util.List; + +import tests.Helper; + + +/* + * @test + * @summary Test basic linking from the run-time image + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g BasicJlinkTest false + */ +public class BasicJlinkTest extends AbstractLinkableRuntimeTest { + + @Override + public void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + Path finalImage = createJavaBaseRuntimeLink(helper, "java-base", isLinkableRuntime); + verifyListModules(finalImage, List.of("java.base")); + } + + private Path createJavaBaseRuntimeLink(Helper helper, String name, boolean isLinkableRuntime) throws Exception { + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder(); + builder.helper(helper) + .name(name) + .addModule("java.base") + .validatingModule("java.base"); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + return createJavaImageRuntimeLink(builder.build()); + } + + public static void main(String[] args) throws Exception { + BasicJlinkTest test = new BasicJlinkTest(); + test.run(); + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/CapturingHandler.java b/test/jdk/tools/jlink/runtimeImage/CapturingHandler.java new file mode 100644 index 0000000000000..b6b6105394df6 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/CapturingHandler.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. + */ + +import jdk.test.lib.process.OutputAnalyzer; + +class CapturingHandler extends AbstractLinkableRuntimeTest.OutputAnalyzerHandler { + + private OutputAnalyzer output; + + public String stdErr() { + return output.getStderr(); + } + + public OutputAnalyzer analyzer() { + return output; + } + + @Override + public void handleAnalyzer(OutputAnalyzer out) { + this.output = out; + } +} \ No newline at end of file diff --git a/test/jdk/tools/jlink/runtimeImage/CustomModuleJlinkTest.java b/test/jdk/tools/jlink/runtimeImage/CustomModuleJlinkTest.java new file mode 100644 index 0000000000000..369bccfecfce4 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/CustomModuleJlinkTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. + */ + +import java.nio.file.Path; +import java.util.List; + +import tests.Helper; + + +/* + * @test + * @summary Test jmod-less jlink with a custom module + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g CustomModuleJlinkTest + */ +public class CustomModuleJlinkTest extends AbstractLinkableRuntimeTest { + + public static void main(String[] args) throws Exception { + CustomModuleJlinkTest test = new CustomModuleJlinkTest(); + test.run(); + } + + @Override + void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + String customModule = "leaf1"; + helper.generateDefaultJModule(customModule); + + // create a base image for linking from the run-time image + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder() + .helper(helper) + .name("cmod-jlink") + .addModule("java.base") + .validatingModule("java.base"); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + Path jlinkImage = createRuntimeLinkImage(builder.build()); + + // Next jlink using the run-time image for java.base, but take + // the custom module from the module path. + Path finalImage = jlinkUsingImage(new JlinkSpecBuilder() + .imagePath(jlinkImage) + .helper(helper) + .name(customModule) + .addModulePath(helper.defaultModulePath(false)) + .expectedLocation(String.format("/%s/%s/com/foo/bar/X.class", customModule, customModule)) + .addModule(customModule) + .validatingModule(customModule) + .build()); + // Expected only the transitive closure of "leaf1" module in the --list-modules + // output of the java launcher. + List expectedModules = List.of("java.base", customModule); + verifyListModules(finalImage, expectedModules); + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/GenerateJLIClassesTest.java b/test/jdk/tools/jlink/runtimeImage/GenerateJLIClassesTest.java new file mode 100644 index 0000000000000..533a8db30d0cc --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/GenerateJLIClassesTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. + */ + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import tests.Helper; + +/* + * @test + * @summary Verify JLI class generation in run-time image link mode + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g GenerateJLIClassesTest + */ +public class GenerateJLIClassesTest extends AbstractLinkableRuntimeTest { + + public static void main(String[] args) throws Exception { + GenerateJLIClassesTest test = new GenerateJLIClassesTest(); + test.run(); + } + + /* + * java.lang.invoke.BoundMethodHandle$Species_* classes get generated + * by the GenerateJLiClassesPlugin. This test ensures that potentially + * generated JLI classes from the run-time image don't populate to the + * target image in the run-time image based link mode. + */ + @Override + void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + Path baseFile = Files.createTempFile("base", "trace"); + String species = "LLLLLLLLLLLLLLLLLLL"; + String fileString = "[SPECIES_RESOLVE] java.lang.invoke.BoundMethodHandle$Species_" + species + " (salvaged)\n"; + Files.write(baseFile, fileString.getBytes(StandardCharsets.UTF_8)); + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder() + .helper(helper) + .addModule("java.base") + .name("jlink.jli-jmodless") + .validatingModule("java.base"); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + + Path runtimeLinkableImage = createRuntimeLinkImage(builder.build()); + // Finally attempt another jmodless link reducing modules to java.base only, + // and asking for specific jli classes. + jlinkUsingImage(new JlinkSpecBuilder() + .helper(helper) + .imagePath(runtimeLinkableImage) + .name("java.base-jli-derived") + .addModule("java.base") + .extraJlinkOpt("--generate-jli-classes=@" + baseFile.toString()) + .expectedLocation("/java.base/java/lang/invoke/BoundMethodHandle$Species_" + species + ".class") + .expectedLocation("/java.base/java/lang/invoke/BoundMethodHandle$Species_L.class") + .unexpectedLocation("/java.base/java/lang/invoke/BoundMethodHandle$Species_" + species.substring(1) + ".class") + .unexpectedLocation("/java.base/java/lang/invoke/BoundMethodHandle$Species_LL.class") + .validatingModule("java.base") + .build()); + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/JImageHelper.java b/test/jdk/tools/jlink/runtimeImage/JImageHelper.java new file mode 100644 index 0000000000000..ba92e1f35f2f2 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/JImageHelper.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023, Red Hat, Inc. + * 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. + */ + + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import jdk.internal.jimage.BasicImageReader; +import jdk.internal.jimage.ImageLocation; + +/** + * + * JDK Modular image iterator + */ +public class JImageHelper { + + private JImageHelper() { + // Don't instantiate + } + + public static List listContents(Path jimage) throws IOException { + try(BasicImageReader reader = BasicImageReader.open(jimage)) { + List entries = new ArrayList<>(); + for (String s : reader.getEntryNames()) { + entries.add(s); + } + Collections.sort(entries); + return entries; + } + } + + public static byte[] getLocationBytes(String location, Path jimage) throws IOException { + try(BasicImageReader reader = BasicImageReader.open(jimage)) { + ImageLocation il = reader.findLocation(location); + byte[] r = reader.getResource(il); + if (r == null) { + throw new IllegalStateException(String.format("bytes for %s not found!", location)); + } + return r; + } + } +} diff --git a/test/jdk/tools/jlink/runtimeImage/JavaSEReproducibleTest.java b/test/jdk/tools/jlink/runtimeImage/JavaSEReproducibleTest.java new file mode 100644 index 0000000000000..d923358aed90d --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/JavaSEReproducibleTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. + */ + +import java.nio.file.Files; +import java.nio.file.Path; + +import tests.Helper; + + +/* + * @test + * @summary Test reproducibility of linking an java.se image using the run-time + * image. + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g JavaSEReproducibleTest + */ +public class JavaSEReproducibleTest extends AbstractLinkableRuntimeTest { + + public static void main(String[] args) throws Exception { + JavaSEReproducibleTest test = new JavaSEReproducibleTest(); + test.run(); + } + + @Override + void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + String javaSeModule = "java.se"; + // create a java.se using jmod-less approach + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder() + .helper(helper) + .addModule(javaSeModule) + .validatingModule(javaSeModule); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + builder.name("java-se-repro1"); + Path javaSEJmodLess1 = createJavaImageRuntimeLink(builder.build()); + + // create another java.se version using jmod-less approach + builder.name("java-se-repro2"); + Path javaSEJmodLess2 = createJavaImageRuntimeLink(builder.build()); + if (Files.mismatch(javaSEJmodLess1.resolve("lib").resolve("modules"), + javaSEJmodLess2.resolve("lib").resolve("modules")) != -1L) { + throw new RuntimeException("jlink producing inconsistent result for " + javaSeModule + " (jmod-less)"); + } + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/KeepPackagedModulesFailTest.java b/test/jdk/tools/jlink/runtimeImage/KeepPackagedModulesFailTest.java new file mode 100644 index 0000000000000..8094579ecd505 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/KeepPackagedModulesFailTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. + */ + +import java.nio.file.Path; +import java.util.function.Predicate; + +import jdk.test.lib.process.OutputAnalyzer; +import tests.Helper; + + +/* + * @test + * @summary Verify that jlink with an empty module path, but trying to use + * --keep-packaged-modules fails as expected. + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g KeepPackagedModulesFailTest + */ +public class KeepPackagedModulesFailTest extends AbstractLinkableRuntimeTest { + + public static void main(String[] args) throws Exception { + KeepPackagedModulesFailTest test = new KeepPackagedModulesFailTest(); + test.run(); + } + + @Override + void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + // create a base image for linking from the run-time image + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder() + .helper(helper) + .name("jlink-fail") + .addModule("java.base") + .validatingModule("java.base"); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + Path baseImage = createRuntimeLinkImage(builder.build()); + + CapturingHandler handler = new CapturingHandler(); + Predicate exitFailPred = new Predicate<>() { + + @Override + public boolean test(OutputAnalyzer t) { + return t.getExitValue() != 0; // expect failure + } + }; + // Attempt a jlink using the run-time image and also using option + // --keep-packaged-modules, which should fail. + jlinkUsingImage(new JlinkSpecBuilder() + .helper(helper) + .imagePath(baseImage) + .name("java-base-jlink-keep-packaged-target") + .addModule("java.base") + .extraJlinkOpt("--keep-packaged-modules=foo") + .validatingModule("java.base") + .build(), handler, exitFailPred); + OutputAnalyzer analyzer = handler.analyzer(); + if (analyzer.getExitValue() == 0) { + throw new AssertionError("Expected jlink to have failed!"); + } + analyzer.stdoutShouldContain("Error"); + analyzer.stdoutShouldContain("--keep-packaged-modules"); + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/ModifiedFilesExitTest.java b/test/jdk/tools/jlink/runtimeImage/ModifiedFilesExitTest.java new file mode 100644 index 0000000000000..709494b62566a --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/ModifiedFilesExitTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. + */ + +import java.nio.file.Path; +import java.util.function.Predicate; + +import jdk.test.lib.process.OutputAnalyzer; +import tests.Helper; + +/* + * @test + * @summary Verify jlink fails by default when linking from the run-time image + * and files have been modified + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g ModifiedFilesExitTest + */ +public class ModifiedFilesExitTest extends ModifiedFilesTest { + + public static void main(String[] args) throws Exception { + ModifiedFilesExitTest test = new ModifiedFilesExitTest(); + test.run(); + } + + @Override + String initialImageName() { + return "java-base-jlink-with-mod-exit"; + } + + @Override + void testAndAssert(Path modifiedFile, Helper helper, Path initialImage) + throws Exception { + CapturingHandler handler = new CapturingHandler(); + Predicate exitFailPred = new Predicate<>() { + + @Override + public boolean test(OutputAnalyzer t) { + return t.getExitValue() != 0; // expect failure + } + }; + jlinkUsingImage(new JlinkSpecBuilder() + .helper(helper) + .imagePath(initialImage) + .name("java-base-jlink-with-mod-exit-target") + .addModule("java.base") + .validatingModule("java.base") + .build(), handler, exitFailPred); + OutputAnalyzer analyzer = handler.analyzer(); + if (analyzer.getExitValue() == 0) { + throw new AssertionError("Expected jlink to fail due to modified file!"); + } + analyzer.stdoutShouldContain(modifiedFile.toString() + " has been modified"); + // Verify the error message is reasonable + analyzer.stdoutShouldNotContain("jdk.tools.jlink.internal.RunImageLinkException"); + analyzer.stdoutShouldNotContain("java.lang.IllegalArgumentException"); + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/ModifiedFilesTest.java b/test/jdk/tools/jlink/runtimeImage/ModifiedFilesTest.java new file mode 100644 index 0000000000000..305fdd3917153 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/ModifiedFilesTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. + */ + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Properties; + +import tests.Helper; + +public abstract class ModifiedFilesTest extends AbstractLinkableRuntimeTest { + + abstract String initialImageName(); + abstract void testAndAssert(Path modifiedFile, Helper helper, Path initialImage) throws Exception; + + @Override + void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder() + .name(initialImageName()) + .addModule("java.base") + .validatingModule("java.base") + .helper(helper); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + Path initialImage = createRuntimeLinkImage(builder.build()); + + Path netPropertiesFile = modifyFileInImage(initialImage); + + testAndAssert(netPropertiesFile, helper, initialImage); + } + + protected Path modifyFileInImage(Path jmodLessImg) + throws IOException, AssertionError { + // modify net.properties config file + Path netPropertiesFile = jmodLessImg.resolve("conf").resolve("net.properties"); + Properties props = new Properties(); + try (InputStream is = Files.newInputStream(netPropertiesFile)) { + props.load(is); + } + String prevVal = (String)props.put("java.net.useSystemProxies", Boolean.TRUE.toString()); + if (prevVal == null || Boolean.getBoolean(prevVal) != false) { + throw new AssertionError("Expected previous value to be false!"); + } + try (OutputStream out = Files.newOutputStream(netPropertiesFile)) { + props.store(out, "Modified net.properties file!"); + } + return netPropertiesFile; + } +} diff --git a/test/jdk/tools/jlink/runtimeImage/ModifiedFilesWarningTest.java b/test/jdk/tools/jlink/runtimeImage/ModifiedFilesWarningTest.java new file mode 100644 index 0000000000000..f52691dd85927 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/ModifiedFilesWarningTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. + */ + +import java.nio.file.Path; + +import jdk.test.lib.process.OutputAnalyzer; +import tests.Helper; + +/* + * @test + * @summary Verify warnings are being produced when linking from the run-time + * image and files have been modified + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g ModifiedFilesWarningTest + */ +public class ModifiedFilesWarningTest extends ModifiedFilesTest { + + protected static final String IGNORE_MODIFIED_RUNTIME_OPT = "--ignore-modified-runtime"; + + public static void main(String[] args) throws Exception { + ModifiedFilesWarningTest test = new ModifiedFilesWarningTest(); + test.run(); + } + + @Override + String initialImageName() { + return "java-base-jlink-with-mod-warn"; + } + + @Override + void testAndAssert(Path modifiedFile, Helper helper, Path initialImage) throws Exception { + CapturingHandler handler = new CapturingHandler(); + jlinkUsingImage(new JlinkSpecBuilder() + .helper(helper) + .imagePath(initialImage) + .name("java-base-jlink-with-mod-warn-target") + .addModule("java.base") + .validatingModule("java.base") + .extraJlinkOpt(IGNORE_MODIFIED_RUNTIME_OPT) // only generate a warning + .build(), handler); + OutputAnalyzer out = handler.analyzer(); + // verify we get the warning message + out.stdoutShouldMatch("Warning: .* has been modified"); + out.stdoutShouldNotContain("java.lang.IllegalArgumentException"); + out.stdoutShouldNotContain("jdk.tools.jlink.internal.RunImageLinkException"); + } +} diff --git a/test/jdk/tools/jlink/runtimeImage/MultiHopTest.java b/test/jdk/tools/jlink/runtimeImage/MultiHopTest.java new file mode 100644 index 0000000000000..88f91f238bd87 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/MultiHopTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. + */ + +import java.nio.file.Path; +import java.util.function.Predicate; + +import jdk.test.lib.process.OutputAnalyzer; +import tests.Helper; + + +/* + * @test + * @summary Verify that a jlink using the run-time image cannot include jdk.jlink + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g MultiHopTest + */ +public class MultiHopTest extends AbstractLinkableRuntimeTest { + + @Override + void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + Path jdkJlinkJmodless = createJDKJlinkJmodLess(helper, "jdk.jlink-multi-hop1", isLinkableRuntime); + CapturingHandler handler = new CapturingHandler(); + Predicate exitFailPred = new Predicate<>() { + + @Override + public boolean test(OutputAnalyzer a) { + return a.getExitValue() != 0; // expect failure + } + }; + jlinkUsingImage(new JlinkSpecBuilder() + .helper(helper) + .imagePath(jdkJlinkJmodless) + .name("jdk-jlink-multi-hop1-target") + .addModule("jdk.jlink") + .validatingModule("java.base") + .build(), handler, exitFailPred); + OutputAnalyzer analyzer = handler.analyzer(); + if (analyzer.getExitValue() == 0) { + throw new AssertionError("Expected jlink to fail due to including jdk.jlink"); + } + String expectedMsg = "This JDK does not contain packaged modules " + + "and cannot be used to create another image with " + + "the jdk.jlink module"; + analyzer.stdoutShouldContain(expectedMsg); + analyzer.stdoutShouldNotContain("Exception"); // ensure error message is sane + } + + private Path createJDKJlinkJmodLess(Helper helper, String name, boolean isLinkableRuntime) throws Exception { + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder(); + builder.helper(helper) + .name(name) + .addModule("jdk.jlink") + .validatingModule("java.base"); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + return createRuntimeLinkImage(builder.build()); + } + + public static void main(String[] args) throws Exception { + MultiHopTest test = new MultiHopTest(); + test.run(); + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/PackagedModulesVsRuntimeImageLinkTest.java b/test/jdk/tools/jlink/runtimeImage/PackagedModulesVsRuntimeImageLinkTest.java new file mode 100644 index 0000000000000..9910be5f91974 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/PackagedModulesVsRuntimeImageLinkTest.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. + */ + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import tests.Helper; +import tests.JImageGenerator; + + +/* + * @test + * @summary Compare packaged-modules jlink with a run-time image based jlink to + * produce the same result + * @requires (jlink.packagedModules & vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g PackagedModulesVsRuntimeImageLinkTest + */ +public class PackagedModulesVsRuntimeImageLinkTest extends AbstractLinkableRuntimeTest { + + public static void main(String[] args) throws Exception { + PackagedModulesVsRuntimeImageLinkTest test = new PackagedModulesVsRuntimeImageLinkTest(); + test.run(); + } + + @Override + void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + // create a java.se using jmod-less approach + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder() + .helper(helper) + .name("java-se-jmodless") + .addModule("java.se") + .validatingModule("java.se"); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + Path javaSEruntimeLink = createJavaImageRuntimeLink(builder.build()); + + // create a java.se using packaged modules (jmod-full) + Path javaSEJmodFull = JImageGenerator.getJLinkTask() + .output(helper.createNewImageDir("java-se-jmodfull")) + .addMods("java.se").call().assertSuccess(); + + compareRecursively(javaSEruntimeLink, javaSEJmodFull); + } + + // Visit all files in the given directories checking that they're byte-by-byte identical + private static void compareRecursively(Path javaSEJmodLess, + Path javaSEJmodFull) throws IOException, AssertionError { + FilesCapturingVisitor jmodFullVisitor = new FilesCapturingVisitor(javaSEJmodFull); + FilesCapturingVisitor jmodLessVisitor = new FilesCapturingVisitor(javaSEJmodLess); + Files.walkFileTree(javaSEJmodFull, jmodFullVisitor); + Files.walkFileTree(javaSEJmodLess, jmodLessVisitor); + List jmodFullFiles = jmodFullVisitor.filesVisited(); + List jmodLessFiles = jmodLessVisitor.filesVisited(); + Collections.sort(jmodFullFiles); + Collections.sort(jmodLessFiles); + + if (jmodFullFiles.size() != jmodLessFiles.size()) { + throw new AssertionError(String.format("Size of files different for jmod-less (%d) vs jmod-full (%d) java.se jlink", jmodLessFiles.size(), jmodFullFiles.size())); + } + String jimageFile = Path.of("lib").resolve("modules").toString(); + // Compare all files except the modules image + for (int i = 0; i < jmodFullFiles.size(); i++) { + String jmodFullPath = jmodFullFiles.get(i); + String jmodLessPath = jmodLessFiles.get(i); + if (!jmodFullPath.equals(jmodLessPath)) { + throw new AssertionError(String.format("jmod-full path (%s) != jmod-less path (%s)", jmodFullPath, jmodLessPath)); + } + if (jmodFullPath.equals(jimageFile)) { + continue; + } + Path a = javaSEJmodFull.resolve(Path.of(jmodFullPath)); + Path b = javaSEJmodLess.resolve(Path.of(jmodLessPath)); + if (Files.mismatch(a, b) != -1L) { + handleFileMismatch(a, b); + } + } + // Compare jimage contents by iterating its entries and comparing their + // paths and content bytes + // + // Note: The files aren't byte-by-byte comparable (probably due to string hashing + // and offset differences in container bytes) + Path jimageJmodLess = javaSEJmodLess.resolve(Path.of("lib")).resolve(Path.of("modules")); + Path jimageJmodFull = javaSEJmodFull.resolve(Path.of("lib")).resolve(Path.of("modules")); + List jimageContentJmodLess = JImageHelper.listContents(jimageJmodLess); + List jimageContentJmodFull = JImageHelper.listContents(jimageJmodFull); + if (jimageContentJmodLess.size() != jimageContentJmodFull.size()) { + throw new AssertionError(String.format("Size of jimage content differs for jmod-less (%d) v. jmod-full (%d)", jimageContentJmodLess.size(), jimageContentJmodFull.size())); + } + for (int i = 0; i < jimageContentJmodFull.size(); i++) { + if (!jimageContentJmodFull.get(i).equals(jimageContentJmodLess.get(i))) { + throw new AssertionError(String.format("Jimage content differs at index %d: jmod-full was: '%s' jmod-less was: '%s'", + i, + jimageContentJmodFull.get(i), + jimageContentJmodLess.get(i) + )); + } + String loc = jimageContentJmodFull.get(i); + if (isTreeInfoResource(loc)) { + // Skip container bytes as those are offsets to the content + // of the container which might be different between jlink runs. + continue; + } + byte[] resBytesFull = JImageHelper.getLocationBytes(loc, jimageJmodFull); + byte[] resBytesLess = JImageHelper.getLocationBytes(loc, jimageJmodLess); + if (resBytesFull.length != resBytesLess.length || Arrays.mismatch(resBytesFull, resBytesLess) != -1) { + throw new AssertionError("Content bytes mismatch for " + loc); + } + } + } + + private static boolean isTreeInfoResource(String path) { + return path.startsWith("/packages") || path.startsWith("/modules"); + } + + private static void handleFileMismatch(Path a, Path b) { + throw new AssertionError("Files mismatch: " + a + " vs. " + b); + } + + static class FilesCapturingVisitor extends SimpleFileVisitor { + private final Path basePath; + private final List filePaths = new ArrayList<>(); + public FilesCapturingVisitor(Path basePath) { + this.basePath = basePath; + } + + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { + Path relative = basePath.relativize(path); + filePaths.add(relative.toString()); + return FileVisitResult.CONTINUE; + } + + List filesVisited() { + return filePaths; + } + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/PatchedJDKModuleJlinkTest.java b/test/jdk/tools/jlink/runtimeImage/PatchedJDKModuleJlinkTest.java new file mode 100644 index 0000000000000..3baa824e04990 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/PatchedJDKModuleJlinkTest.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. + */ + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.Predicate; +import java.util.spi.ToolProvider; + +import jdk.test.lib.process.OutputAnalyzer; +import tests.Helper; + + +/* + * @test + * @summary Test run-time link with --patch-module. Expect failure. + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g PatchedJDKModuleJlinkTest + */ +public class PatchedJDKModuleJlinkTest extends AbstractLinkableRuntimeTest { + + @Override + public void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + String imageName = "java-base-patched"; + Path runtimeLinkImage = createRuntimeLinkImage(helper, imageName + "-base", isLinkableRuntime); + + // Prepare patched module content + Path patchSource = Path.of("java-base-patch-src"); + Path pkg = patchSource.resolve("java", "lang"); + Path extraClass = pkg.resolve("MyJlinkPatchInteger.java"); + String source = """ + package java.lang; + public class MyJlinkPatchInteger { + public int add(int a, int b) { + return a + b; + } + } + """; + Files.createDirectories(pkg); + Files.writeString(extraClass, source); + Path patchClasses = Path.of("java-base-patch-classes"); + Files.createDirectories(patchClasses); + ToolProvider javac = ToolProvider.findFirst("javac") + .orElseThrow(() -> new AssertionError("javac not found")); + javac.run(System.out, System.err, new String[] { + "-d", patchClasses.toString(), + "--patch-module=java.base=" + patchSource.toAbsolutePath().toString(), + extraClass.toAbsolutePath().toString() + }); + + // Perform a run-time image link expecting a failure + CapturingHandler handler = new CapturingHandler(); + Predicate exitFailPred = new Predicate<>() { + + @Override + public boolean test(OutputAnalyzer t) { + return t.getExitValue() != 0; // expect failure + } + }; + jlinkUsingImage(new JlinkSpecBuilder() + .helper(helper) + .imagePath(runtimeLinkImage) + .name(imageName + "-derived") + .addModule("java.base") + .validatingModule("java.base") + .extraJlinkOpt("-J--patch-module=java.base=" + + patchClasses.toAbsolutePath().toString()) + .build(), handler, exitFailPred); + OutputAnalyzer analyzer = handler.analyzer(); + if (analyzer.getExitValue() == 0) { + throw new AssertionError("Expected jlink to fail due to patched module!"); + } + analyzer.stdoutShouldContain("MyJlinkPatchInteger.class not found in the modules image."); + analyzer.stdoutShouldContain("--patch-module is not supported"); + // Verify the error message is reasonable + analyzer.stdoutShouldNotContain("jdk.tools.jlink.internal.RunImageLinkException"); + analyzer.stdoutShouldNotContain("java.lang.IllegalArgumentException"); + } + + private Path createRuntimeLinkImage(Helper helper, String name, boolean isLinkableRuntime) throws Exception { + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder() + .name(name) + .addModule("java.base") + .validatingModule("java.base") + .helper(helper); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + return createRuntimeLinkImage(builder.build()); + } + + public static void main(String[] args) throws Exception { + PatchedJDKModuleJlinkTest test = new PatchedJDKModuleJlinkTest(); + test.run(); + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/SystemModulesTest.java b/test/jdk/tools/jlink/runtimeImage/SystemModulesTest.java new file mode 100644 index 0000000000000..fac8cac112d14 --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/SystemModulesTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. + */ + +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +import tests.Helper; +import tests.JImageValidator; + + +/* + * @test + * @summary Test appropriate handling of generated SystemModules* classes in run-time image link mode + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g SystemModulesTest + */ +public class SystemModulesTest extends AbstractLinkableRuntimeTest { + + public static void main(String[] args) throws Exception { + SystemModulesTest test = new SystemModulesTest(); + test.run(); + } + + /* + * SystemModule classes are module specific. If the jlink is based on the + * modules image, then earlier generated SystemModule classes shall not get + * propagated. + */ + @Override + void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + // create an image with a module containing a main entrypoint (jdk.httpserver), + // thus producing the SystemModules$0.class. Add jdk.jdwp.agent as a module which + // isn't resolved by default, so as to generate SystemModules$default.class + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder() + .helper(helper) + .name("httpserver-jlink-jmodless-derived") + .addModule("jdk.httpserver") + .addModule("jdk.jdwp.agent") + .validatingModule("java.base"); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + Path javaseJmodless = createJavaImageRuntimeLink(builder.build()); + // Verify that SystemModules$0.class etc. are there, due to httpserver and jdwp.agent + JImageValidator.validate(javaseJmodless.resolve("lib").resolve("modules"), + List.of("/java.base/jdk/internal/module/SystemModules$default.class", + "/java.base/jdk/internal/module/SystemModules$0.class", + "/java.base/jdk/internal/module/SystemModules$all.class"), + Collections.emptyList()); + } + +} diff --git a/test/jdk/tools/jlink/runtimeImage/SystemModulesTest2.java b/test/jdk/tools/jlink/runtimeImage/SystemModulesTest2.java new file mode 100644 index 0000000000000..6be4ad7321cea --- /dev/null +++ b/test/jdk/tools/jlink/runtimeImage/SystemModulesTest2.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. + * 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. + */ + +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +import tests.Helper; +import tests.JImageValidator; + + +/* + * @test + * @summary Test disabled SystemModulesPlugin in run-time image link mode. Expect + * generated classes to not be there. + * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @library ../../lib /test/lib + * @enablePreview + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jimage + * @build tests.* jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.process.ProcessTools + * @run main/othervm -Xmx1g SystemModulesTest2 + */ +public class SystemModulesTest2 extends AbstractLinkableRuntimeTest { + + public static void main(String[] args) throws Exception { + SystemModulesTest2 test = new SystemModulesTest2(); + test.run(); + } + + @Override + void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { + // See SystemModulesTest which enables the system-modules plugin. With + // it disabled, we expect for the generated classes to not be there. + BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder() + .helper(helper) + .name("jlink-jmodless-sysmod2") + .addModule("jdk.httpserver") + .validatingModule("java.base") + .addExtraOption("--disable-plugin") + .addExtraOption("system-modules"); + if (isLinkableRuntime) { + builder.setLinkableRuntime(); + } + Path runtimeImageLinkTarget = createJavaImageRuntimeLink(builder.build()); + JImageValidator.validate(runtimeImageLinkTarget.resolve("lib").resolve("modules"), + Collections.emptyList(), + List.of("/java.base/jdk/internal/module/SystemModules$all.class", + "/java.base/jdk/internal/module/SystemModules$default.class", + "/java.base/jdk/internal/module/SystemModules$0.class")); + } + +} diff --git a/test/jdk/tools/lib/tests/Helper.java b/test/jdk/tools/lib/tests/Helper.java index 0acf6ec8bf3c3..337d767b0b221 100644 --- a/test/jdk/tools/lib/tests/Helper.java +++ b/test/jdk/tools/lib/tests/Helper.java @@ -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 @@ -64,20 +64,30 @@ public class Helper { private final Map> moduleDependencies = new HashMap<>(); private final List bootClasses; private final FileSystem fs; + private final boolean linkableRuntime; + private static final Path JDK_HOME = Paths.get(System.getProperty("test.jdk")); public static Helper newHelper() throws IOException { - Path jdkHome = Paths.get(System.getProperty("test.jdk")); - if (!Files.exists(jdkHome.resolve("jmods"))) { + return newHelper(false); + } + + public static Helper newHelper(boolean linkableRuntime) throws IOException { + if (!linkableRuntime && !jdkHasPackagedModules()) { // Skip test if the jmods directory is missing (e.g. exploded image) System.err.println("Test not run, NO jmods directory"); return null; } - return new Helper(jdkHome); + return new Helper(JDK_HOME, linkableRuntime); + } + + public static boolean jdkHasPackagedModules() { + return Files.exists(JDK_HOME.resolve("jmods")); } - private Helper(Path jdkHome) throws IOException { + private Helper(Path jdkHome, boolean linkableRuntime) throws IOException { + this.linkableRuntime = linkableRuntime; this.stdjmods = jdkHome.resolve("jmods").normalize(); - if (!Files.exists(stdjmods)) { + if (!linkableRuntime && !Files.exists(stdjmods)) { throw new IOException("Standard jMods do not exist."); } this.fs = FileSystems.getFileSystem(URI.create("jrt:/")); @@ -140,7 +150,8 @@ public String defaultModulePath() { } public String defaultModulePath(boolean includeStdMods) { - return (includeStdMods? stdjmods.toAbsolutePath().toString() : "") + File.pathSeparator + String standardMods = linkableRuntime ? "" : stdjmods.toAbsolutePath().toString() + File.pathSeparator; + return (includeStdMods? standardMods : "") + jmods.toAbsolutePath().toString() + File.pathSeparator + jars.toAbsolutePath().toString() + File.pathSeparator + explodedmodsclasses.toAbsolutePath().toString(); @@ -184,7 +195,7 @@ public Result generateDefaultJModule(String moduleName, List classNames, generateGarbage(jmodsclasses.resolve(moduleName)); Path jmodFile = jmods.resolve(moduleName + ".jmod"); - JModTask task = JImageGenerator.getJModTask() + JModTask task = JImageGenerator.getJModTask(linkableRuntime) .jmod(jmodFile) .addJmods(stdjmods) .addJmods(jmods.toAbsolutePath()) diff --git a/test/jdk/tools/lib/tests/JImageGenerator.java b/test/jdk/tools/lib/tests/JImageGenerator.java index 330547148d02f..b872ca4f5842d 100644 --- a/test/jdk/tools/lib/tests/JImageGenerator.java +++ b/test/jdk/tools/lib/tests/JImageGenerator.java @@ -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 @@ -159,7 +159,11 @@ private static void copy(InputStream in, OutputStream out) throws IOException { } public static JModTask getJModTask() { - return new JModTask(); + return getJModTask(false); + } + + public static JModTask getJModTask(boolean linkableRuntime) { + return new JModTask(linkableRuntime); } public static JLinkTask getJLinkTask() { @@ -350,11 +354,16 @@ public static class JModTask { private final List jars = new ArrayList<>(); private final List jmods = new ArrayList<>(); private final List options = new ArrayList<>(); + private final boolean linkableRuntime; private Path output; private String hashModules; private String mainClass; private String moduleVersion; + private JModTask(boolean linkableRuntime) { + this.linkableRuntime = linkableRuntime; + } + public JModTask addNativeLibraries(Path cp) { this.libs.add(cp); return this; @@ -414,7 +423,7 @@ private String modulePath() { // This is expect FIRST jmods THEN jars, if you change this, some tests could fail String jmods = toPath(this.jmods); String jars = toPath(this.jars); - return jmods + File.pathSeparator + jars; + return linkableRuntime ? jars : jmods + File.pathSeparator + jars; } private String toPath(List paths) { diff --git a/test/jtreg-ext/requires/VMProps.java b/test/jtreg-ext/requires/VMProps.java index 4f00846116cb6..775b95959c6e1 100644 --- a/test/jtreg-ext/requires/VMProps.java +++ b/test/jtreg-ext/requires/VMProps.java @@ -138,6 +138,7 @@ public Map call() { map.put("jdk.containerized", this::jdkContainerized); map.put("vm.flagless", this::isFlagless); map.put("jdk.foreign.linker", this::jdkForeignLinker); + map.put("jlink.packagedModules", this::packagedModules); vmGC(map); // vm.gc.X = true/false vmGCforCDS(map); // may set vm.gc vmOptFinalFlags(map); @@ -715,6 +716,21 @@ private String jdkContainerized() { return "" + "true".equalsIgnoreCase(isEnabled); } + private String packagedModules() { + // Some jlink tests require packaged modules being present (jmods). + // For a runtime linkable image build packaged modules aren't present + try { + Path jmodsDir = Path.of(System.getProperty("java.home"), "jmods"); + if (jmodsDir.toFile().exists()) { + return Boolean.TRUE.toString(); + } else { + return Boolean.FALSE.toString(); + } + } catch (Throwable t) { + return Boolean.FALSE.toString(); + } + } + /** * Checks if we are in almost out-of-box configuration, i.e. the flags * which JVM is started with don't affect its behavior "significantly". diff --git a/test/langtools/jdk/jshell/AnalyzeSnippetTest.java b/test/langtools/jdk/jshell/AnalyzeSnippetTest.java index 3e2e1a839e2bd..a9b0315e96058 100644 --- a/test/langtools/jdk/jshell/AnalyzeSnippetTest.java +++ b/test/langtools/jdk/jshell/AnalyzeSnippetTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 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 @@ -23,7 +23,7 @@ /* * @test - * @bug 8182270 + * @bug 8182270 8341176 * @summary test non-eval Snippet analysis * @build KullaTesting TestingInputStream * @run testng AnalyzeSnippetTest @@ -32,8 +32,10 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.List; +import java.util.stream.Stream; import jdk.jshell.Snippet; import jdk.jshell.DeclarationSnippet; +import jdk.jshell.Diag; import org.testng.annotations.Test; import jdk.jshell.JShell; @@ -52,6 +54,7 @@ import jdk.jshell.TypeDeclSnippet; import jdk.jshell.VarSnippet; import static jdk.jshell.Snippet.SubKind.*; +import jdk.jshell.SourceCodeAnalysis.SnippetWrapper; @Test public class AnalyzeSnippetTest { @@ -141,6 +144,49 @@ public void testErroneous() { SubKind.UNKNOWN_SUBKIND); } + public void testDiagnosticsForSourceSnippet() { + Snippet sn; + sn = assertSnippet("unknown()", UNKNOWN_SUBKIND); + assertDiagnostics(sn, "0-7:compiler.err.cant.resolve.location.args"); + sn = assertSnippet("new String(null, )", UNKNOWN_SUBKIND); + assertDiagnostics(sn, "17-17:compiler.err.illegal.start.of.expr"); + sn = assertSnippet("1 + ", UNKNOWN_SUBKIND); + assertDiagnostics(sn, "3-3:compiler.err.premature.eof"); + sn = assertSnippet("class C {", UNKNOWN_SUBKIND); + assertDiagnostics(sn, "9-9:compiler.err.premature.eof"); + sn = assertSnippet("class C {}", CLASS_SUBKIND); + assertDiagnostics(sn); + sn = assertSnippet("void t() { throw new java.io.IOException(); }", METHOD_SUBKIND); + assertDiagnostics(sn, "11-43:compiler.err.unreported.exception.need.to.catch.or.throw"); + sn = assertSnippet("void t() { unknown(); }", METHOD_SUBKIND); + assertDiagnostics(sn, "11-18:compiler.err.cant.resolve.location.args"); + sn = assertSnippet("import unknown.unknown;", SINGLE_TYPE_IMPORT_SUBKIND); + assertDiagnostics(sn, "7-22:compiler.err.doesnt.exist"); + } + + public void testSnippetWrapper() { + SourceCodeAnalysis analysis = state.sourceCodeAnalysis(); + Snippet sn; + String code = "unknown()"; + sn = assertSnippet(code, UNKNOWN_SUBKIND); + SnippetWrapper wrapper = analysis.wrapper(sn); + String wrapped = wrapper.wrapped(); + assertEquals(wrapped, """ + package REPL; + + class $JShell$DOESNOTMATTER { + public static java.lang.Object do_it$() throws java.lang.Throwable { + return unknown(); + } + } + """); + for (int pos = 0; pos < code.length(); pos++) { + int wrappedPos = wrapper.sourceToWrappedPosition(pos); + assertEquals(wrapped.charAt(wrappedPos), code.charAt(pos)); + assertEquals(wrapper.wrappedToSourcePosition(wrappedPos), pos); + } + } + public void testNoStateChange() { assertSnippet("int a = 5;", SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND); assertSnippet("a", SubKind.UNKNOWN_SUBKIND); @@ -159,4 +205,14 @@ private Snippet assertSnippet(String input, SubKind sk) { assertEquals(sn.subKind(), sk); return sn; } + + private String diagToString(Diag d) { + return d.getStartPosition() + "-" + d.getEndPosition() + ":" + d.getCode(); + } + + private void assertDiagnostics(Snippet s, String... expectedDiags) { + List actual = state.diagnostics(s).map(this::diagToString).toList(); + List expected = List.of(expectedDiags); + assertEquals(actual, expected); + } } diff --git a/test/langtools/tools/javac/plugin/AutostartPlugins.java b/test/langtools/tools/javac/plugin/AutostartPlugins.java index eeac5e0ecc608..5eb5b16bad533 100644 --- a/test/langtools/tools/javac/plugin/AutostartPlugins.java +++ b/test/langtools/tools/javac/plugin/AutostartPlugins.java @@ -30,7 +30,7 @@ * jdk.compiler/com.sun.tools.javac.main * jdk.jlink * @build toolbox.ToolBox toolbox.JavacTask toolbox.JarTask - * @run main AutostartPlugins + * @run main/othervm AutostartPlugins */ import java.io.IOException; diff --git a/test/langtools/tools/javac/plugin/InternalAPI.java b/test/langtools/tools/javac/plugin/InternalAPI.java index 523c9365c99c8..70bb024ede9a4 100644 --- a/test/langtools/tools/javac/plugin/InternalAPI.java +++ b/test/langtools/tools/javac/plugin/InternalAPI.java @@ -31,7 +31,7 @@ * jdk.compiler/com.sun.tools.javac.main * jdk.jlink * @build toolbox.ToolBox toolbox.JavacTask toolbox.JarTask - * @run main InternalAPI + * @run main/othervm InternalAPI */ import java.io.IOException; diff --git a/test/lib-test/jdk/test/lib/security/FixedSecureRandomTest.java b/test/lib-test/jdk/test/lib/security/FixedSecureRandomTest.java new file mode 100644 index 0000000000000..bdebc9e0a8c89 --- /dev/null +++ b/test/lib-test/jdk/test/lib/security/FixedSecureRandomTest.java @@ -0,0 +1,49 @@ +/* + * 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. + */ +import jdk.test.lib.Asserts; +import jdk.test.lib.Utils; +import jdk.test.lib.security.FixedSecureRandom; + +/* + * @test + * @library /test/lib + * @summary ensure FixedSecureRandom works as expected + */ +public class FixedSecureRandomTest { + public static void main(String[] args) throws Exception { + var fsr = new FixedSecureRandom(new byte[] {1, 2, 3}, + new byte[] {4, 5, 6}); + var b1 = new byte[2]; + fsr.nextBytes(b1); + Asserts.assertEqualsByteArray(b1, new byte[] {1, 2}); + Asserts.assertTrue(fsr.hasRemaining()); + fsr.nextBytes(b1); + Asserts.assertEqualsByteArray(b1, new byte[] {3, 4}); + Asserts.assertTrue(fsr.hasRemaining()); + fsr.nextBytes(b1); + Asserts.assertEqualsByteArray(b1, new byte[] {5, 6}); + Asserts.assertFalse(fsr.hasRemaining()); + Utils.runAndCheckException(() -> fsr.nextBytes(b1), + IllegalStateException.class); + } +} diff --git a/test/lib/jdk/test/lib/security/FixedSecureRandom.java b/test/lib/jdk/test/lib/security/FixedSecureRandom.java new file mode 100644 index 0000000000000..cfe8ccc212f4a --- /dev/null +++ b/test/lib/jdk/test/lib/security/FixedSecureRandom.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.test.lib.security; + +import java.io.ByteArrayOutputStream; +import java.security.SecureRandom; + +/// A custom implementation of `SecureRandom` that outputs a +/// predefined sequence of bytes. +/// +/// The `FixedSecureRandom` class is designed for testing and +/// controlled environments where predictable output is required. +/// Upon creation, the class is initialized with a fixed byte array. +/// Each call to `nextBytes()` will return these bytes in sequence, +/// ensuring that the output matches the provided input exactly. +/// An `IllegalStateException` will be thrown when the predefined +/// bytes are exhausted. +public class FixedSecureRandom extends SecureRandom { + + private byte[] buffer; + private int offset; + + // Multiple segments of ordered predefined bytes can be + // provided for convenience. For example, ML-KEM.KeyGen + // requires 2 blocks of 32-byte random data. + public FixedSecureRandom(byte[]... data) { + var os = new ByteArrayOutputStream(); + for (byte[] b : data) { + os.writeBytes(b); + } + buffer = os.toByteArray(); + offset = 0; + } + + @Override + public void nextBytes(byte[] bytes) { + if (bytes.length > buffer.length - offset) { + throw new IllegalStateException("Not enough bytes"); + } + System.arraycopy(buffer, offset, bytes, 0, bytes.length); + offset += bytes.length; + } + + /// {@return whether there are remaining used bytes} + /// + /// This method is useful to detect whether an algorithm + /// implementation has indeed consumed the required number + /// of bytes correctly. + public boolean hasRemaining() { + return offset != buffer.length; + } +} diff --git a/test/micro/org/openjdk/bench/java/lang/StringBuilders.java b/test/micro/org/openjdk/bench/java/lang/StringBuilders.java index ed5c0d30db866..e41bd361ff5f3 100644 --- a/test/micro/org/openjdk/bench/java/lang/StringBuilders.java +++ b/test/micro/org/openjdk/bench/java/lang/StringBuilders.java @@ -54,6 +54,8 @@ public class StringBuilders { private StringBuilder sbLatin2; private StringBuilder sbUtf16; private StringBuilder sbUtf17; + private int[] intsArray; + private long[] longArray; @Setup public void setup() { @@ -69,6 +71,13 @@ public void setup() { sbLatin2 = new StringBuilder("Latin1 string"); sbUtf16 = new StringBuilder("UTF-\uFF11\uFF16 string"); sbUtf17 = new StringBuilder("UTF-\uFF11\uFF16 string"); + int size = 16; + intsArray = new int[size]; + longArray = new long[size]; + for (int i = 0; i < longArray.length; i++) { + intsArray[i] = ((100 * i + i) << 24) + 4543 + i * 4; + longArray[i] = ((100L * i + i) << 32) + 4543 + i * 4L; + } } @Benchmark @@ -224,6 +233,47 @@ public String toStringCharWithInt8() { return result.toString(); } + @Benchmark + public int appendWithIntLatin1() { + StringBuilder buf = sbLatin1; + buf.setLength(0); + for (long l : longArray) { + buf.append(l); + } + return buf.length(); + } + + @Benchmark + public int appendWithIntUtf16() { + StringBuilder buf = sbUtf16; + buf.setLength(0); + buf.setLength(0); + for (long l : longArray) { + buf.append(l); + } + return buf.length(); + } + + @Benchmark + public int appendWithLongLatin1() { + StringBuilder buf = sbLatin1; + buf.setLength(0); + for (long l : longArray) { + buf.append(l); + } + return buf.length(); + } + + @Benchmark + public int appendWithLongUtf16() { + StringBuilder buf = sbUtf16; + buf.setLength(0); + buf.setLength(0); + for (long l : longArray) { + buf.append(l); + } + return buf.length(); + } @Benchmark public int appendWithBool8Latin1() {